مطالب
الگویی برای مدیریت دسترسی همزمان به ConcurrentDictionary
ConcurrentDictionary، ساختار داده‌ای است که امکان افزودن، دریافت و حذف عناصری را به آن به صورت thread-safe میسر می‌کند. اگر در برنامه‌ای نیاز به کار با یک دیکشنری توسط چندین thread وجود داشته باشد، ConcurrentDictionary راه‌حل مناسبی برای آن است.
اکثر متدهای این کلاس thread-safe طراحی شده‌اند؛ اما با یک استثناء: متد GetOrAdd آن thread-safe نیست:
 TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);


بررسی نحوه‌ی کار با متد GetOrAdd

این متد یک کلید را دریافت کرده و سپس بررسی می‌کند که آیا این کلید در مجموعه‌ی جاری وجود دارد یا خیر؟ اگر کلید وجود داشته باشد، مقدار متناظر با آن بازگشت داده می‌شود و اگر خیر، delegate ایی که به عنوان پارامتر دوم آن معرفی شده‌است، اجرا خواهد شد، سپس مقدار بازگشت داده شده‌ی توسط آن به مجموعه اضافه شده و در آخر این مقدار به فراخوان بازگشت داده می‌شود.
var dictionary = new ConcurrentDictionary<string, string>();
 
var value = dictionary.GetOrAdd("key1", x => "item 1");
Console.WriteLine(value);
 
value = dictionary.GetOrAdd("key1", x => "item 2");
Console.WriteLine(value);
در این مثال زمانیکه اولین GetOrAdd فراخوانی می‌شود، مقدار item 1 بازگشت داده خواهد شد و همچنین این مقدار را در مجموعه‌ی جاری، به کلید key1 انتساب می‌دهد. در دومین فراخوانی، چون key1 در دیکشنری، دارای مقدار است، همان را بازگشت می‌دهد و دیگر به value factory ارائه شده مراجعه نخواهد کرد. بنابراین خروجی این مثال به صورت ذیل است:
item 1
item 1


دسترسی همزمان به متد GetOrAdd امن نیست

ConcurrentDictionary برای اغلب متدهای آن به صورت توکار مباحث قفل‌گذاری چند ریسمانی را اعمال می‌کند؛ اما نه برای متد GetOrAdd. زمانیکه valueFactory آن در حال اجرا است، دسترسی همزمان به آن thread-safe نیست و ممکن است بیش از یکبار فراخوانی شود.
یک مثال:
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var dictionary = new ConcurrentDictionary<int, int>();
            var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
            var addStack = new ConcurrentStack<int>();

            Parallel.For(1, 1000, options, i =>
            {
                var key = i % 10;
                dictionary.GetOrAdd(key, k =>
                {
                    addStack.Push(k);
                    return i;
                });
            });

            Console.WriteLine($"dictionary.Count: {dictionary.Count}");
            Console.WriteLine($"addStack.Count: {addStack.Count}");
        }
    }
}
یک نمونه خروجی این مثال می‌تواند به صورت ذیل باشد:
dictionary.Count: 10
addStack.Count: 13
در اینجا هر چند 10 آیتم در دیکشنری ذخیره شده‌اند، اما عملیاتی که در value factory متد GetOrAdd آن صورت گرفته، 13 بار اجرا شده‌است (بجای 10 بار).
علت اینجا است که در این بین، متد GetOrAdd توسط ترد A فراخوانی می‌شود، اما key را در دیکشنری جاری پیدا نمی‌کند. به همین جهت شروع به اجرای valueFactory آن خواهد کرد. در همین زمان ترد B نیز به دنبال همین key است. ترد قبلی هنوز به پایان کار خودش نرسیده‌است که مجددا valueFactory متعلق به همین key اجرا خواهد شد. به همین جهت است که در ConcurrentStack اجرا شده‌ی در valueFactory، بیش از 10 آیتم موجود هستند.


الگویی برای مدیریت دسترسی همزمان امن به متد GetOrAdd‌

یک روش برای دسترسی همزمان امن به متد GetOrAdd، توسط تیم ASP.NET Core به صورت ذیل ارائه شده‌است:
// 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the pipeline more
// once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple
// threads but only one of the objects succeeds in creating a pipeline.
private readonly ConcurrentDictionary<Type, Lazy<RequestDelegate>> _pipelinesCache = 
new ConcurrentDictionary<Type, Lazy<RequestDelegate>>();
در اینجا با استفاده از کلاس Lazy، از ایجاد چندین pipeline به ازای یک key مشخص جلوگیری شده‌است.
یک مثال:
namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var dictionary = new ConcurrentDictionary<int, Lazy<int>>();
            var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
            var addStack = new ConcurrentStack<int>();

            Parallel.For(1, 1000, options, i =>
            {
                var key = i % 10;
                dictionary.GetOrAdd(key, k => new Lazy<int>(() =>
                {
                    addStack.Push(k);
                    return i;
                }));
            });

            // Access the dictionary values to create lazy values.
            foreach (var pair in dictionary)
                Console.WriteLine(pair.Value.Value);

            Console.WriteLine($"dictionary.Count: {dictionary.Count}");
            Console.WriteLine($"addStack.Count: {addStack.Count}");
        }
    }
}
با این خروجی:
10
1
2
3
4
5
6
7
8
9
dictionary.Count: 10
addStack.Count: 10
اینبار، هم dictionary و هم addStack دارای 10 عضو هستند که به معنای تنها اجرای 10 بار value factory است و نه بیشتر.
در این مثال دو تغییر صورت گرفته‌اند:
الف) مقادیر ConcurrentDictionary به صورت Lazy معرفی شده‌اند.
ب) متد GetOrAdd نیز یک مقدار Lazy را بازگشت می‌دهد.

زمانیکه از اشیاء Lazy استفاده می‌شود، خروجی‌های بازگشتی از GetOrAdd، توسط این اشیاء Lazy محصور خواهند شد. اما نکته‌ی مهم اینجا است که هنوز value factory آن‌ها فراخوانی نشده‌است. این فراخوانی تنها زمانی صورت می‌گیرد که به خاصیت Value یک شیء Lazy دسترسی پیدا کنیم و این دسترسی نیز به صورت thread-safe طراحی شده‌است. یعنی حتی اگر چند ترد new Lazy یک key مشخص را بازگشت دهند، تنها یکبار value factory متد GetOrAdd با دسترسی به خاصیت Value این اشیاء Lazy فراخوانی می‌شود و مابقی تردها منتظر مانده و تنها مقدار ذخیره شده‌ی در دیکشنری را دریافت می‌کنند و سبب اجرای مجدد value factory سنگین و زمانبر آن، نخواهند شد.

بر این مبنا می‌توان یک LazyConcurrentDictionary را نیز به صورت ذیل طراحی کرد:
    public class LazyConcurrentDictionary<TKey, TValue>
    {
        private readonly ConcurrentDictionary<TKey, Lazy<TValue>> _concurrentDictionary;
        public LazyConcurrentDictionary()
        {
            _concurrentDictionary = new ConcurrentDictionary<TKey, Lazy<TValue>>();
        }

        public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
        {
            var lazyResult = _concurrentDictionary.GetOrAdd(key,
             k => new Lazy<TValue>(() => valueFactory(k), LazyThreadSafetyMode.ExecutionAndPublication));
            return lazyResult.Value;
        }
    }
در اینجا ممکن است چندین ترد همزمان متد GetOrAdd را دقیقا با یک کلید مشخص فراخوانی کنند؛ اما تنها چندین شیء Lazy بسیار سبک که هنوز اطلاعات محصور شده‌ی توسط آن‌ها اجرا نشده‌است، ایجاد خواهند شد. اولین تردی که به خاصیت Value آن دسترسی پیدا کند، سبب اجرای delegate زمانبر و سنگین آن شده و مابقی تردها مجبور به منتظر ماندن جهت بازگشت این نتیجه از دیکشنری خواهند شد (و نه اجرای مجدد delegate).
در مثال فوق، به صورت صریحی پارامتر LazyThreadSafetyMode نیز مقدار دهی شده‌است. هدف از آن اطمینان حاصل کردن از آغاز این شیء Lazy با دسترسی به خاصیت Value آن، تنها توسط یک ترد است.

نمونه‌ی دیگر کار با خاصیت ویژه‌ی Value شیء Lazy را در مطلب «پشتیبانی توکار از ایجاد کلاس‌های Singleton از دات نت 4 به بعد» پیشتر در این سایت مطالعه کرده‌اید.
نظرات مطالب
نقدی بر کتاب «مرجع کامل entity framework 4.1»
چون عموما تیم‌های سورس باز، از لشگر مستند ساز و مستند نویس مایکروسافت محروم هستند.
برای مثال یادم هست زمانیکه سیلورلایت 5 بتا ارائه شد (چند وقت قبل)، همان روز حدود بالای 30 مقاله‌ی بلند بالا در مورد تازه‌های محصولی که دقیقا همان روز در یک کنفرانس برای اولین بار معرفی شده، مطلب منتشر شد. خوب ... این یک لشگر سازماندهی شده است. رقابت کردن با این‌ها سخت است.
شما فکر می‌کنید کسانی که کتاب‌های بعدی سیلورلایت 5 را منتشر می‌کنند از کجا مطالب خودشون رو تامین می‌کنند؟ همین 30 تا مقاله رو کنار هم قرار می‌دهند با نگارش خودشون منتشر می‌کنند. راحت میشه نصف یک کتاب.
NHibernate هم به همین صورت، این لشگر مستند ساز رو نداره. به علاوه خیلی اشتباه است اگر تصور کنید NHibernate همان Hibernate جاوا است. خیلی اضافات در NHibernate به دلیل پیشرفت‌های زبان‌های دات نتی وجود دارد که در Hibernate نیست (همین مباحث static reflection ، lambda expression ، LINQ و غیره). خلاصه اینکه NHibernate فقط یک معادل یک به یک، یکی از کتابخانه‌ها‌ی معروف جاوا نیست. شاید نگارش اول آن اینطور بوده.

ضمنا فعلا شما همین کتاب EF 4.1 رو بخرید! اگر به چاپ دوم رسید یعنی می‌شود به انتشار کتاب‌های مشابه امیدوار شد!
مطالب
ارسال خودکار مطلب به بلاگر

اکثر خدمات گوگل دارای API هم هستند و به این ترتیب با استفاده از برنامه نویسی نیز می‌توان به آن‌ها دسترسی پیدا کرد. برای نمونه API دسترسی به Blogger در اینجا توضیح داده شده است. برای کار با این امکانات یا می‌توان چرخ را از نو اختراع کرد یا از کتابخانه‌های مرتبطی همانند Gdata API for .NET استفاده نمود. برای دات نت فریم ورک، از آدرس http://code.google.com/p/google-gdata/ می‌توان آخرین کتابخانه‌های کار با GData یا Google Data API را دریافت کرد. برای نمونه فایل Google_Data_API_Setup_1.9.0.0.msi فعلی آن حدود 28 مگ حجم دارد و به درد کسانی می‌خورد که علاقمند هستند تا تمام امکانات موجود آن‌را بررسی کنند. راه ساده‌تری هم برای دسترسی به این کتابخانه‌ها وجود دارد؛ می‌توان از NuGet استفاده کرد.


به این ترتیب به سادگی و سرعت هرچه تمامتر فایل 200 کیلوبایتی Google.GData.Client.dll دریافت شده و ارجاعی نیز به آن اضافه خواهد شد. همین حد جهت کار با بلاگر کافی است.
برای نمونه قطعه کد زیر کار ارسال یک مطلب جدید به وبلاگ بلاگری شما را انجام خواهد داد:

using System;
using System.Collections.Generic;
using Google.GData.Client;

namespace BloggerAutoPoster
{
public class BloggerAutoPoster
{
public string UserName { set; get; }

public string Password { set; get; }

public string PostTitle { set; get; }

public IList<string> PostTags { set; get; }

public string PostBody { set; get; }

public string BlogUrl { set; get; }

public bool PostAsDraft { set; get; }

public bool PostNewEntry()
{
var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(UserName, Password)
};
var newPost = constructNewEntry();
var result = service.Insert(new Uri(BlogUrl), newPost);
return result != null;
}

private AtomEntry constructNewEntry()
{
var newPost = new AtomEntry
{
Title = { Text = PostTitle },
Content = new AtomContent
{
Content = string.Format(@"<div xmlns=""http://www.w3.org/1999/xhtml"">{0}</div>", PostBody),
Type = "xhtml"
},
IsDraft = PostAsDraft
};

foreach (var tag in PostTags)
{
newPost.Categories.Add(
new AtomCategory
{
Term = tag,
Scheme = "http://www.blogger.com/atom/ns#"
});
}

return newPost;
}
}
}

مثالی از استفاده آن هم به صورت زیر می‌باشد:

new BloggerAutoPoster
{
BlogUrl = "https://www.blogger.com/feeds/number/posts/default",
UserName = "name@gmail.com",
Password = "pass",
PostTitle = "بررسی ارسل خودکار-3",
PostTags = new List<string> { "بررسی ارسال خودکار" },
PostBody = "تست می‌شود123",
PostAsDraft = false
}.PostNewEntry();

نام کاربری و کلمه عبور آن، همان مشخصات وارد شدن به اکانت جی‌میل شما است. اگر می‌خواهید مطلب ارسالی بلافاصله در سایت ظاهر نشود PostAsDraft را true کنید. همچنین BlogUrl آن، همانطور که ملاحظه می‌کنید فرمت خاصی دارد. جهت یافتن آن می‌توان از قطعه کد زیر کمک گرفت:

using System;
using System.Collections.Generic;
using System.Linq;
using Google.GData.Client;

namespace BloggerAutoPoster
{
public class BlogInfo
{
public string Title { set; get; }
public string Url { set; get; }
}

public class BloggerInfo
{
public static IList<BlogInfo> FindMyBlogsUrls(string username, string password)
{
var result = new List<BlogInfo>();

var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(username, password)
};

var query = new FeedQuery { Uri = new Uri("https://www.blogger.com/feeds/default/blogs") };
var feed = service.Query(query);

if (feed == null)
throw new NotSupportedException("You don't have any blogs!");

foreach (var entry in feed.Entries)
{
result.AddRange(entry.Links.Where(t => t.Rel.Equals("http://schemas.google.com/g/2005#post"))
.Select(t => new BlogInfo
{
Url = new Uri(t.HRef.ToString()).AbsoluteUri,
Title = entry.Title.Text
}));
}

return result;
}
}
}

توسط کد فوق، آدرس ویژه و عنوان تمام بلاگ‌های ثبت شده‌ی بلاگری شما بازگشت داده می‌شود.


مطالب
پشتیبانی توکار از ایجاد کلاس‌های Singleton از دات نت 4 به بعد
روش‌های زیادی برای ایجاد یک وهله‌ی Singleton وجود دارند. وهله‌ای که در طول عمر یک برنامه، تنها یکبار ایجاد شده و حفظ می‌شود. برای مثال شاید متداول‌ترین حالت آن که در بسیاری از کدها دیده می‌شود، تعریف یک متغیر استاتیک در کلاس، غیرعمومی تعریف کردن سازنده‌ی کلاس و وهله سازی این فیلد استاتیک در صورت نال بودن آن است:
    public class WrongSingleton
    {
        static WrongSingleton _instance;

        WrongSingleton()
        {
        }

        public static WrongSingleton Instance
        {
            get { return _instance ?? (_instance = new WrongSingleton()); }
        }
    }
هرچند این روش کار می‌کند اما thread-safe نیست. به این معنا که ممکن است دو ترد در آن واحد به بررسی قسمت ?? instance_ بپردازند و چون هنوز نال است، دوبار وهله سازی کلاس، با فراخوانی new WrongSingleton صورت خواهد گرفت و هر ترد در آن لحظه به وهله‌ی متفاوتی دسترسی خواهد داشت.
راه حل‌های زیادی برای رفع این مشکل با اعمال مباحث قفل گذاری تا نکات ریز مربوط به کامپایلر وجود دارند که لیست آن‌ها را در اینجا می‌توانید مطالعه کنید.

از دات نت 4 به بعد با معرفی الگوی Lazy، امکان پیاده سازی lazy thread safe singletons به صورت توکار در دسترس می‌باشد. نمونه‌ای از آن در کدهای IoC Container معروفی به نام StructureMap بکار رفته‌است:
    public class Container
    {
        // ...
    }

    public static class ObjectFactory
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);

        public static Container Container
        {
            get { return _containerBuilder.Value; }
        }

        private static Container defaultContainer()
        {
            return new Container();
        }
    }
در اینجا کلاس ObjectFactory یک وهله از کلاس Container را در اختیار مصرف کننده قرار می‌دهد؛ با این شرایط:
- چون این وهله توسط کلاس Lazy محصور شده‌است، صرفا در اولین بار دسترسی به آن، نمونه سازی خواهد شد. این مورد سبب کاهش مصرف حافظه‌ی برنامه و همچنین بالا رفتن سرعت برپایی اولیه‌ی آن می‌شود. بسیاری از اشیایی که در یک برنامه تعریف می‌شوند، شاید الزاما جهت ارائه راه حلی برای مساله‌ای خاص، مورد استفاده قرار نگیرند. تعریف آن‌ها به صورت Lazy، سربار نمونه سازی الزامی آن‌ها را حذف خواهد کرد و آن‌را به اولین بار استفاده از شیء مورد نظر، به تعویق خواهد انداخت.
- با استفاده از LazyThreadSafetyMode.ExecutionAndPublication، چندین ترد می‌توانند به خاصیت Container دسترسی پیدا کنند، اما تنها یکی از آن‌ها موفق به وهله سازی این کلاس خواهد شد. البته حالت ExecutionAndPublication، حالت پیش فرض است و الزاما نیازی به ذکر آن نیست.

اینبار بازنویسی کلاس ابتدای بحث با توجه به نکات ذکر شده به صورت زیر خواهد بود:
    public sealed class LazySingleton
    {
        private static readonly Lazy<LazySingleton> _instance =
            new Lazy<LazySingleton>(() => new LazySingleton(), LazyThreadSafetyMode.ExecutionAndPublication);

        private LazySingleton()
        {
        }

        public static LazySingleton Instance
        {
            get { return _instance.Value; }
        }
    }
- در آن سازنده‌ی کلاس، خصوصی تعریف شده‌است.
- تنها وهله‌ی در دسترس کلاس، به صورت استاتیک و نمونه سازی کلاس، توسط کلاس Lazy با پارامتر LazyThreadSafetyMode.ExecutionAndPublication انجام می‌شود.
- علت استفاده از lambda در سازنده‌ی کلاس Lazy، امکان دسترسی به اعضای private کلاس، از طریق یک خاصیت static است.
مطالب
مروری سریع بر اصول مقدماتی MVVM

در قسمت قبل، فلسفه وجودی MVVM و MVC و امثال آن‌را به بیانی ساده مطالعه کردید. همچنین به اینجا رسیدیم که بجای نوشتن روال رخدادگردان، از Commands استفاده کنید.
در این قسمت «تفکر MVVM ایی» بررسی خواهد شد! بنابراین سطح این قسمت را هم مقدماتی درنظر بگیرید.

در سیستم متداول مایکروسافتی ما همیشه یک فرم داریم به همراه یک سری کنترل. برای استفاده از این‌ها هم در فایل code behind فرم مرتبط، امکان دسترسی به این کنترل‌ها وجود دارد. مثلا textBox1.Text یعنی ارجاعی مستقیم به شیء textBox1 و سپس دسترسی به خاصیت متنی آن.
«تفکر MVVM ایی» می‌گه که: خیر! اینکار رو نکنید؛ ارجاع مستقیم به یک کنترل روش کار من نیست! فرم رو طراحی کنید؛ برای هیچکدام از کنترل‌ها هم نامی را مشخص نکنید (برخلاف رویه متداول). یک فایل درست کنید به نام Model ، داخل آن معادل textBox1.Text را که می‌خواهید استفاده کنید، پیش بینی و تعریف کنید؛ مثلا Public string Name . همین!
ما نمی‌خواهیم بدانیم که اصلا textBox1 وجود خارجی دارد یا نه. ما فقط با خاصیت متنی آن که در ادامه نحوه‌ی سیم کشی آن‌را هم بررسی خواهیم کرد، کار داریم.
بنابراین بجای اینکه بنویسید:

<TextBox Name="txtName" />

که ممکن است بعدا وسوسه شوید تا از txtName.Text آن استفاده کنید، بنویسید:

<TextBox Text="{Binding Name}" />

این مهم‌ترین قسمت «تفکر MVVM ایی» به زبان ساده است. یعنی قرار است تا حد ممکن از Binding استفاده کنیم. مثلا در قسمت قبل هم دیدید که بجای نوشتن روال رخدادگردان، فرمان مرتبط با آن‌را به جای دیگری Bind کردیم.

بنابراین تا اینجا Model ما به این شکل خواهد بود:

using System.ComponentModel;

namespace SL5Tests
{
public class MainPageModel : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
raisePropertyChanged("Name");
}
}

public event PropertyChangedEventHandler PropertyChanged;
void raisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}


سؤال مهم:
تا اینجا یک فایل Model داریم که خاصیت Name در آن تعریف شده؛ یک فرم (View) هم داریم که فقط در آن نوشته شده Binding Name. الان این‌ها چطور به هم متصل خواهند شد؟
پاسخ: اینجا است که کلاس دیگری به نام ViewModel (همان فایل Code behind قدیمی است با این تفاوت که به هیچ فرم خاصی گره نخورده است و اصلا نمی‌داند که در برنامه فرمی وجود دارد یا نه)، کار خودش را شروع خواهد کرد:

namespace SL5Tests
{
public class MainPageViewModel
{
public MainPageModel MainPageModelData { set; get; }
public MainPageViewModel()
{
MainPageModelData = new MainPageModel();
MainPageModelData.Name = "Test1";
}
}
}

ما در این کلاس یک وهله از MainPageModel را ایجاد خواهیم کرد. اگر فرمی (که ما دقیقا نمی‌دانیم کدام فرم) در برنامه نیاز به یک ViewModel بر اساس مدل یاد شده داشت، می‌تواند آن‌را مورد استفاده قرار دهد. مقدار دهی آن در ViewModel موجب مقدار دهی خاصیت Text در فرم مرتبط خواهد شد و برعکس (البته به شرطی که مدل ما INotifyPropertyChanged را پیاده سازی کرده باشد و در فرم برنامه Binding Mode دو طرفه تعریف شود).

در قسمت بعد هم کار اتصال نهایی صورت می‌گیرد:
ابتدا xmlns:VM تعریف می‌شود تا بتوان به ViewModelها در طرف XAML دسترسی پیدا کرد. سپس در قسمت مثلا UserControl.Resources، این ViewModel را تعریف کرده و به عنوان DataContext بالاترین شیء فرم مقدار دهی خواهیم کرد:

<UserControl x:Class="SL5Tests.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:VM="clr-namespace:SL5Tests"
mc:Ignorable="d" Language="fa"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<VM:MainPageViewModel x:Name="vmMainPageViewModel" />
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource vmMainPageViewModel}}"
x:Name="LayoutRoot"
Background="White">
<TextBox Text="{Binding
MainPageModelData.Name,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>

اکنون اگر یک breakpoint روی این سطر Binding قرار دهیم و برنامه را اجرا کنیم، جزئیات این سیم کشی را در عمل بهتر می‌توان مشاهده کرد:


البته این قابلیت قرار دادن breakpoint روی Bindingهای تعریف شده در View فعلا به سیلورلایت 5 اضافه شده و هنوز در WPF موجود نیست.

حداقل مزیتی را که اینجا می‌توان مشاهده کرد این است که فایل MainPageViewModel چون نمی‌داند که قرار است در کدام View وهله سازی شود، به سادگی در Viewهای دیگر نیز قابل استفاده خواهد بود یا تغییر و تعویض کلی View آن کار ساده‌ای است.
Commanding قسمت قبل را هم اینجا می‌شود اضافه کرد. تعاریف DelegateCommandهای مورد نیاز در ViewModel قرار می‌گیرند. مابقی عملیات تفاوتی نمی‌کند و یکسان است.

مطالب دوره‌ها
شی گرایی در #F
برنامه نویسی شی گرای سومین نسل از الگوهای اصلی برنامه نویسی است. در توضیحات فصل اول گفته شد که #F یک زبان تابع گرا است ولی این بدان معنی نیست که #F از مفاهیمی نظیر کلاس و یا interface پشتیبانی نکند. برعکس در #F امکان تعریف کلاس و interface و هم چنین پیاده سازی مفاهیم شی گرایی وجود دارد.

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

Classes
کلاس چارچوبی از اشیا است برای نگهداری خواص(Properties) و رفتار ها(Methods) و رخدادها(Events). کلاس پایه ای‌ترین مفهوم در برنامه نویسی شی گراست. ساختار کلی تعربف کلاس در #F به صورت زیر است:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
   [ class ]
     [ inherit base-type-name(base-constructor-args) ]
     [ let-bindings ]
     [ do-bindings ]
     member-list
      ...
   [ end ]

type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
همان طور که در ساختار بالا می‌بینید مفاهیم access-modifier و inherit و constructor هم در #F وجود دارد.

انواع access-modifier در #F
  • public : دسترسی برای تمام فراخوان‌ها امکان پذیر است
  • internal : دسترسی برای تمام فراخوان هایی که در همین assembly هستند امکان پذیر است
  • private : دسترسی فقط برای فراخوان‌های موجود در همین ماژول امکان پذیر است

نکته : protected access modifier در #F پشتیبانی نمی‌شود.

مثالی از تعریف کلاس:

type Account(number : int, name : string) = class
    let mutable amount = 0m
   
end
کلاس بالا دارای یک سازنده است که دو پارامتر ورودی می‌گیرد. کلمه end به معنای انتهای کلاس است. برای استفاده کلاس باید به صورت زیر عمل کنید:
let myAccount = new Account(123456, "Masoud")
توابع و خواص در کلاس ها
برای تعریف خاصیت در #F باید از کلمه کلیدی member استفاده کنید. در مثال بعدی برای کلاس بالا تابع و خاصیت تعریف خواهیم کرد.
type Account(number : int, name: string) = class
    let mutable amount = 0m
 
    member x.Number = number
    member x.Name= name
    member x.Amount = amount
 
    member x.Deposit(value) = amount <- amount + value
    member x.Withdraw(value) = amount <- amount - value
end
کلاس بالا دارای سه خاصیت به نام‌های Number و Name و Amount است و دو تابع به نام‌های Deposit و Withdraw دارد. اما x استفاده شده قبل از هر member به معنی this در #C  است. در #F شما برای اشاره به شناسه‌های یک محدوده خودتون باید یک نام رو برای اشاره گر مربوطه تعیین کنید.
open System
 
type Account(number : int, name: string) = class
    let mutable amount = 0m
 
    member x.Number = number
    member x.Name= name
    member x.Amount = amount
 
    member x.Deposit(value) = amount <- amount + value
    member x.Withdraw(value) = amount <- amount - value
end
 let masoud= new Account(12345, "Masoud") let saeed = new Account(67890, "Saeed") let transfer amount (source : Account) (target : Account) = source.Withdraw amount target.Deposit amount let printAccount (x : Account) = printfn "x.Number: %i, x.Name: %s, x.Amount: %M" x.Number x.Name x.Amount let main() = let printAccounts() = [masoud; saeed] |> Seq.iter printAccount printfn "\nInializing account" homer.Deposit 50M marge.Deposit 100M printAccounts() printfn "\nTransferring $30 from Masoud to Saeed" transfer 30M masoud saeed
 printAccounts() printfn "\nTransferring $75 from Saeed to Masoud" transfer 75M saeed masoud printAccounts() main()
استفاده از کلمه do
در #F زمانی که قصد داشته باشیم در بعد از وهله سازی از کلاس و فراخوانی سازنده، عملیات خاصی انجام شود(مثل انجام برخی عملیات متداول در سازنده‌های کلاس‌های دات نت) باید از کلمه کلیدی do به همراه یک بلاک از کد استفاده کنیم.
open System
open System.Net
 
type Stock(symbol : string) = class

    let mutable _symbol = String.Empty
    do
     //کد مورد نظر در این جا نوشته  میشود
end
یک مثال در این زمینه:

open System

type MyType(a:int, b:int) as this =
    inherit Object()
    let x = 2*a
    let y = 2*b
    do printfn "Initializing object %d %d %d %d %d %d"
               a b x y (this.Prop1) (this.Prop2)
    static do printfn "Initializing MyType." 
    member this.Prop1 = 4*x
    member this.Prop2 = 4*y
    override this.ToString() = System.String.Format("{0} {1}", this.Prop1, this.Prop2)

let obj1 = new MyType(1, 2)
در مثال بالا دو عبارت do  یکی به صورت static و دیگری به صورت غیر static تعریف شده اند. استفاده از do  به صورت غیر static این امکان را به ما می‌دهد که بتوانیم به تمام شناسه‌ها و توابع تعریف شده در کلاس استفاده کنیم ولی do به صورت static فقط به خواص و توابع از نوع static در کلاس دسترسی دارد.
خروجی مثال بالا:
Initializing MyType.
Initializing object 1 2 2 4 8 16
خواص static:
برای تعریف خواص به صورت استاتیک مانند #C از کلمه کلیدی static استفاده کنید.مثالی در این زمینه:
type SomeClass(prop : int) = class
    member x.Prop = prop
    static member SomeStaticMethod = "This is a static method"
end
SomeStaticMethod به صورت استاتیک تعریف شده در حالی که x.Prop به صورت غیر استاتیک. دسترسی به متد‌ها یا خواص static باید بدون وهله سازی از کلاس انجام بگیرد در غیر این صورت با خطای کامپایلر روبرو خواهید شد.
let instance = new SomeClass(5);;
instance.SomeStaticMethod;; 

output:
stdin(81,1): error FS0191: property 'SomeStaticMethod' is static.
روش استفاده درست:
SomeClass.SomeStaticMethod;; (* invoking static method *)
متد‌های get , set در خاصیت ها:
همانند #C و سایر زبان‌های دات نت امکان تعریف متد‌های get و set برای خاصیت‌های یک کلاس وجود دارد.
ساختار کلی:
 member alias.PropertyName
        with get() = some-value
        and set(value) = some-assignment
مثالی در این زمینه:
type MyClass() = class
   let mutable num = 0 
    member x.Num
        with get() = num
        and set(value) = num <- value
end;;
کد متناظر در #C:
public int Num
{
   get{return num;}
   set{num=value;}
}
یا به صورت:
type MyClass() = class
    let mutable num = 0
 
    member x.Num
        with get() = num
        and set(value) =
            if value > 10 || value < 0 then
                raise (new Exception("Values must be between 0 and 10"))
            else
                num <- value
end

Interface ها
اینترفیس به تمامی خواص و توابع عمومی اشئایی که آن را پیاده سازی کرده اند اشاره می‌کند. (توضیحات بیشتر (^ ) و (^ ))ساختار کلی برای تعریف آن به صورت زیر است:
type type-name = 
   interface
       inherits-decl 
       member-defns 
   end
مثال:
type IPrintable =
   abstract member Print : unit -> unit
استفاده از حرف I برای شروع نام اینترفیس طبق قوانین تعریف شده (اختیاری) برای نام گذاری است.
نکته: در هنگام تعریف توابع و خاصیت در interface‌ها باید از کلمه abstract استفاده کنیم. هر کلاسی که از یک یا چند تا اینترفیس ارث ببرد باید تمام خواص و توابع اینتریس‌ها را پیاده سازی کند. در مثال بعدی کلاس SomeClass1 اینترفیس بالا را پیاده سازی می‌کند. دقت کنید که کلمه this توسط من به عنوان اشاره گر به اشیای کلاس تعیین شده و شما می‌تونید از هر کلمه یا حرف دیگری استفاده کنید.
type SomeClass1(x: int, y: float) =
   interface IPrintable with 
      member this.Print() = printfn "%d %f" x y
نکته مهم: اگر قصد فراخوانی متد Print را در کلاس بالا دارید نمی‌تونید به صورت مستقیم متد بالا را فراخوانی کنید. بلکه حتما باید کلاس به اینترفیس مربوطه cast شود.
روش نادرست:
let instance = new SomeClass1(10,20)
instance.Print//فراخوانی این متد باعث ایجاد خطای کامپایلری می‌شود.
روش درست:
let instance = new SomeClass1(10,20) 
let instanceCast = instance :> IPrintable// استفاده از (<:)  برای عملیات تبدیل کلاس به اینترفیس
instanceCast.Print
برای عملیات cast ازاستفاده کنید.
در مثال بعدی کلاسی خواهیم داشت که از سه اینترفیس ارث می‌برد. در نتیجه باید تمام متد‌های هر سه اینترفیس را پیاده سازی کند.
type Interface1 =
    abstract member Method1 : int -> int

type Interface2 =
    abstract member Method2 : int -> int

type Interface3 =
    inherit Interface1
    inherit Interface2
    abstract member Method3 : int -> int

type MyClass() =
    interface Interface3 with 
        member this.Method1(n) = 2 * n
        member this.Method2(n) = n + 100
        member this.Method3(n) = n / 10
فراخوانی این متد‌ها نیز به صورت زیر خواهد بود:
let instance = new MyClass()
let instanceToCast = instance :> Interface3
instanceToCast.Method3 10
کلاس‌های Abstract
#F از کلاس‌های abstract هم پشتیبانی می‌کند. اگر با کلاس‌های abstract در #C آشنایی ندارید می‌تونید مطالب مورد نظر رو در  (^ ) و (^ ) مطالعه کنید. به صورت خلاصه کلاس‌های abstract به عنوان کلاس‌های پایه در برنامه نویسی شی گرا استفاده می‌شوند. این کلاس‌ها دارای خواص و متد‌های پیاده سازی شده و نشده هستند. خواص و متد هایی که در کلاس پایه abstract پیاده سازی نشده اند باید توسط کلاس هایی که از این کلاس پایه ارث می‌برند حتما پیاده سازی شوند.
ساختار کلی تعریف کلاس‌های abstract:
[<AbstractClass>]
type [ accessibility-modifier ] abstract-class-name =
    [ inherit base-class-or-interface-name ]
    [ abstract-member-declarations-and-member-definitions ]

    abstract member member-name : type-signature
در #F برای این که مشخص کنیم که یک کلاس abstract است حتما باید [<AbstractClass>] در بالای کلاس تعریف شود.
[<AbstractClass>]
type Shape(x0 : float, y0 : float) =
    let mutable x, y = x0, y0
    let mutable rotAngle = 0.0

    abstract Area : float with get
    abstract Perimeter : float  with get
    abstract Name : string with get
کلاس بالا تعریفی از کلاس abstract است که سه خصوصیت abstract دارد (برای تعیین خصوصیت‌ها و متد هایی که در کلاس پایه پیاده سازی نمی‌شوند از کلمه کلیدی abstract در هنگام تعریف آن‌ها استفاده می‌کنیم). حال دو کلاس ایجاد می‌کنیم که این کلاس پایه را پیاده سازی کنند.

#1 کلاس اول
type Square(x, y,SideLength) =
    inherit Shape(x, y)
  override this.Area = this.SideLength * this.SideLength override this.Perimeter = this.SideLength * 4. override this.Name = "Square"
#2 کلاس دوم
type Circle(x, y, radius) =
    inherit Shape(x, y)
 let PI = 3.141592654 member this.Radius = radius override this.Area = PI * this.Radius * this.Radius override this.Perimeter = 2. * PI * this.Radius
Structures
structure‌ها در #F دقیقا معال struct در #C هستند. توضیحات بیشتر درباره struct در #C (^ ) و (^ )). اما به طور خلاصه باید ذکر کنم که strucure‌ها تقریبا دارای مفهوم کلاس هستند با اندکی تفاوت که شامل موارد زیر است:
  • structure‌ها از نوع مقداری هستند و این بدین معنی است مستقیما درون پشته ذخیره می‌شوند.
  • ارجاع به structure‌ها از نوع ارجاع با مقدار است بر خلاف کلاس‌ها که از نوع ارجاع به منبع هستند.(^ )
  • structure‌ها دارای خواص ارث بری نیستند.
  • عموما از structure برای ذخیره مجموعه ای از داده‌ها با حجم و اندازه کم استفاده می‌شود.

ساختار کلی تعریف structure

[ attributes ]
type [accessibility-modifier] type-name =
   struct
      type-definition-elements
   end

//یا به صورت زیر

[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
   type-definition-elements
یک نکته مهم هنگام کار با struct‌ها در #F این است که امکان استفاده از let و Binding در struct‌ها وجود ندارد. به جای آن باید از val استفاده کنید.
type Point3D =
   struct 
      val x: float
      val y: float
      val z: float
   end
تفاوت اصلی بین val و let در این است که هنگام تعریف شناسه با val امکان مقدار دهی اولیه به شناسه وجود ندارد. در مثال بالا مقادیر برای x و y و z برابر 0.0 است که توسط کامپایلر انجام می‌شود. در ادامه یک struct به همراه سازنده تعریف می‌کنیم:
type Point2D =
   struct 
      val X: float
      val Y: float
      new(x: float, y: float) = { X = x; Y = y }
   end
توسط سازنده struct بالا مقادیر اولیه x و y دریافت می‌شود به متغیر‌های متناظر انتساب می‌شود.

  در پایان یک مثال مشترک رو در #C و #F پیاده سازی می‌کنیم:


مطالب
معرفی کتابخانه Postal برای ASP.NET MVC
Postal کتابخانه ای برای تولید و ارسال ایمیل توسط نما‌های ASP.NET MVC است. برای شروع این کتابخانه را به پروژه خود اضافه کنید. پنجره Package Manager Console  را باز کرده و فرمان زیر را اجرا کنید.
PM> Install-Package Postal

شروع به کار با Postal

نحوه استفاده از Postal در کنترلر‌های خود را در کد زیر مشاهده می‌کنید.
using Postal;

public class HomeController : Controller
{
  public ActionResult Index()
  {
      dynamic email = new Email("Example");
      email.To = "webninja@example.com";
      email.FunnyLink = DB.GetRandomLolcatLink();
      email.Send();
      return View();
  }
}
Postal نمای ایمیل را در مسیر Views\Emails\Example.cshtml جستجو می‌کند.
To: @ViewBag.To
From: lolcats@website.com
Subject: Important Message

Hello,
You wanted important web links right?
Check out this: @ViewBag.FunnyLink

<3


پیکربندی SMTP

Postal ایمیل‌ها را توسط SmtpClient ارسال می‌کند که در فریم ورک دات نت موجود است. تنظیمات SMTP را می‌توانید در فایل web.config خود پیکربندی کنید. برای اطلاعات بیشتر به MSDN Documentation مراجعه کنید.
<configuration>
  ...
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="network">
        <network host="example.org" port="25" defaultCredentials="true"/>
      </smtp>
    </mailSettings>
  </system.net>
  ...
</configuration>

ایمیل‌های Strongly-typed

همه خوششان نمی‌آید از آبجکت‌های دینامیک استفاده کنند. علاوه بر آن آبجکت‌های دینامیک مشکلاتی هم دارند. مثلا قابلیت IntelliSense و یا Compile-time error را نخواهید داشت.
قدم اول - کلاسی تعریف کنید که از Email ارث بری می‌کند.
namespace App.Models
{
  public class ExampleEmail : Email
  {
    public string To { get; set; }
    public string Message { get; set; }
  }
}
قدم دوم - از این کلاس استفاده کنید!
public void Send()
{
  var email = new ExampleEmail
  {
    To = "hello@world.com",
    Message = "Strong typed message"
  };
  email.Send();
}
قدم سوم - نمایی ایجاد کنید که از مدل شما استفاده می‌کند. نام نما، بر اساس نام کلاس مدل انتخاب شده است. بنابراین مثلا ExampleEmail نمایی با نام Example.cshtml لازم دارد.
@model App.Models.ExampleEmail
To: @Model.To
From: postal@example.com
Subject: Example

Hello,
@Model.Message
Thanks!

آزمون‌های واحد (Unit Testing)

هنگام تست کردن کدهایی که با Postal کار می‌کنند، یکی از کارهایی که می‌خواهید انجام دهید حصول اطمینان از ارسال شدن ایمیل‌ها است. البته در بدنه تست‌ها نمی‌خواهیم هیچ ایمیلی ارسال شود.
Postal یک قرارداد بنام IEmailService و یک پیاده سازی پیش فرض از آن بنام EmailService ارائه می‌کند، که در واقع ایمیل‌ها را ارسال هم می‌کند. با در نظر گرفتن این پیش فرض که شما از یک IoC Container استفاده می‌کنید (مانند StructureMap, Ninject)، آن را طوری پیکربندی کنید تا یک نمونه از IEmailService به کنترلر‌ها تزریق کند. سپس از این سرویس برای ارسال آبجکت‌های ایمیل‌ها استفاده کنید (بجای فراخوانی متد ()Email.Send).
public class ExampleController : Controller 
{
    public ExampleController(IEmailService emailService)
    {
        this.emailService = emailService;
    }

    readonly IEmailService emailService;

    public ActionResult Index()
    {
        dynamic email = new Email("Example");
        // ...
        emailService.Send(email);
        return View();
    }
}
این کنترلر را با ساختن یک Mock از اینترفیس IEmailService تست کنید. یک مثال با استفاده از FakeItEasy را در زیر مشاهده می‌کنید.
[Test]
public void ItSendsEmail()
{
    var emailService = A.Fake<IEmailService>();
    var controller = new ExampleController(emailService);
    controller.Index();
    A.CallTo(() => emailService.Send(A<Email>._))
     .MustHaveHappened();
}

ایمیل‌های ساده و HTML

Postal ارسال ایمیل‌های ساده (plain text) و HTML را بسیار ساده می‌کند.
قدم اول - نمای اصلی را بسازید. این نما header‌ها را خواهد داشت و نما‌های مورد نیاز را هم رفرنس می‌کند. مسیر نما Views\Emails\Example.cshtml\~ است.
To: test@test.com
From: example@test.com
Subject: Fancy email
Views: Text, Html
قدوم دوم - نمای تکست را ایجاد کنید. به قوانین نامگذاری دقت کنید، Example.cshtml به Example.Text.cshtml تغییر یافته. مسیر فایل Views\Emails\Example.Text.cshtml است.
Content-Type: text/plain; charset=utf-8

Hello @ViewBag.PersonName,
This is a message
دقت داشته باشید که تنها یک Content-Type باید تعریف کنید.
قدم سوم - نمای HTML را ایجاد کنید (باز هم فقط با یک Content-Type). مسیر فایل Views\Emails\Example.Html.cshtml\~ است.
Content-Type: text/html; charset=utf-8

<html>
  <body>
    <p>Hello @ViewBag.PersonName,</p>
    <p>This is a message</p>
  </body>
</html>

ضمیمه ها

برای افزودن ضمائم خود به ایمیل ها، متد Attach را فراخوانی کنید.
dynamic email = new Email("Example");
email.Attach(new Attachment("c:\\attachment.txt"));
email.Send();


جاسازی تصاویر در ایمیل ها

Postal یک HTML Helper دارد که امکان جاسازی (embedding) تصاویر در ایمیل‌ها را فراهم می‌کند. دیگر نیازی نیست به یک URL خارجی اشاره کنید. 
ابتدا مطمئن شوید که فایل web.config شما فضای نام Postal را اضافه کرده است. این کار دسترسی به HTML Helper مذکور در نمای‌های ایمیل را ممکن می‌سازد.
<configuration>
  <system.web.webPages.razor>
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="Postal" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>
</configuration>
متد EmbedImage تصویر مورد نظر را در ایمیل شما جاسازی می‌کند و توسط یک تگ </img> آن را رفرنس می‌کند.
To: john@example.org
From: app@example.org
Subject: Image

@Html.EmbedImage("~/content/postal.jpg")
Postal سعی می‌کند تا نام فایل تصویر را، بر اساس مسیر تقریبی ریشه اپلیکیشن شما تعیین کند.


Postal بیرون از ASP.NET

Postal می‌تواند نماهای ایمیل‌ها را بیرون از فضای ASP.NET رندر کند. مثلا در یک اپلیکیشن کنسول یا یک سرویس ویندوز.
این امر توسط یک View Engine سفارشی میسر می‌شود. تنها نماهای Razor پشتیبانی می‌شوند. نمونه کدی را در زیر مشاهده می‌کنید.
using Postal;

class Program
{
    static void Main(string[] args)
    {
        // Get the path to the directory containing views
        var viewsPath = Path.GetFullPath(@"..\..\Views");

        var engines = new ViewEngineCollection();
        engines.Add(new FileSystemRazorViewEngine(viewsPath));

        var service = new EmailService(engines);

        dynamic email = new Email("Test");
        // Will look for Test.cshtml or Test.vbhtml in Views directory.
        email.Message = "Hello, non-asp.net world!";
        service.Send(email);
    }
}

محدودیت ها: نمی توانید برای نمای ایمیل هایتان از Layout‌ها استفاده کنید. همچنین در نماهای خود تنها از مدل‌ها (Models) می‌توانید استفاده کنید، و نه ViewBag.


Email Headers:  برای در بر داشتن نام، در آدرس ایمیل از فرمت زیر استفاده کنید.

To: John Smith <john@example.org>
Multiple Values: برخی از header‌ها می‌توانند چند مقدار داشته باشند. مثلا Bcc و CC. اینگونه مقادیر را می‌توانید به دو روش در نمای خود تعریف کنید:
جدا کردن مقادیر با کاما:
Bcc: john@smith.com, harry@green.com
Subject: Example

etc
و یا تکرار header:
Bcc: john@smith.com
Bcc: harry@green.com
Subject: Example

etc

ساختن ایمیل بدون ارسال آن

لازم نیست برای ارسال ایمیل هایتان به Postal تکیه کنید. در عوض می‌توانید یک آبجکت از نوع System.Net.Mail.MailMessage تولید کنید و به هر نحوی که می‌خواهید آن را پردازش کنید. مثلا شاید بخواهید بجای ارسال ایمیل ها، آنها را به یک صف پیام مثل MSMQ انتقال دهید یا بعدا توسط سرویس دیگری ارسال شوند. این آبجکت MailMessage تمامی Header ها، محتوای اصلی ایمیل و ضمائم را در بر خواهد گرفت.
کلاس EmailService در Postal متدی با نام CreateMailMessage فراهم می‌کند.
public class ExampleController : Controller 
{
    public ExampleController(IEmailService emailService)
    {
        this.emailService = emailService;
    }

    readonly IEmailService emailService;

    public ActionResult Index()
    {
        dynamic email = new Email("Example");
        // ...

        var message = emailService.CreateMailMessage(email);
        CustomProcessMailMessage(message);        

        return View();
    }
}

در این پست با امکانات اصلی کتابخانه Postal آشنا شدید و دیدید که به سادگی می‌توانید ایمیل‌های Razor بسازید. برای اطلاعات بیشتر لطفا به سایت پروژه Postal  مراجعه کنید.
مطالب
ساده سازی و بالا بردن سرعت عملیات Reflection با استفاده از Dynamic Proxy
فرض کنید یک چنین کلاسی طراحی شده‌است:
public class NestedClass
{
    private int _field2;
    public NestedClass()
    {
        _field2 = 12;
    }
}
 
public class MyClass
{
    private int _field1;
    private NestedClass _nestedClass;
 
    public MyClass()
    {
        _field1 = 1;
        _nestedClass = new NestedClass();
    }
 
    private string GetData()
    {
        return "Test";
    }
}
می‌خواهیم از طریق Reflection مقادیر فیلدها و متدهای مخفی آن‌را بخوانیم.
حالت متداول دسترسی به فیلد خصوصی آن از طریق Reflection، یک چنین شکلی را دارد:
var myClass = new MyClass();
 
var field1Obj = myClass.GetType().GetField("_field1", BindingFlags.NonPublic | BindingFlags.Instance);
if (field1Obj != null)
{
    Console.WriteLine(Convert.ToInt32(field1Obj.GetValue(myClass)));
}
و یا دسترسی به مقدار خروجی متد خصوصی آن، به نحو زیر است:
var getDataMethod = myClass.GetType().GetMethod("GetData", BindingFlags.NonPublic | BindingFlags.Instance);
if (getDataMethod != null)
{
    Console.WriteLine(getDataMethod.Invoke(myClass, null));
}
در اینجا دسترسی به مقدار فیلد مخفی NestedClass، شامل مراحل زیر است:
var nestedClassObj = myClass.GetType().GetField("_nestedClass", BindingFlags.NonPublic | BindingFlags.Instance);
if (nestedClassObj != null)
{
    var nestedClassFieldValue = nestedClassObj.GetValue(myClass);
    var field2Obj = nestedClassFieldValue.GetType()
        .GetField("_field2", BindingFlags.NonPublic | BindingFlags.Instance);
    if (field2Obj != null)
    {
        Console.WriteLine(Convert.ToInt32(field2Obj.GetValue(nestedClassFieldValue)));
    }
}
البته این مقدار کد فقط برای دسترسی به دو سطح تو در تو بود.

چقدر خوب بود اگر می‌شد بجای این همه کد، نوشت:
myClass._field1
myClass._nestedClass._field2
myClass.GetData()
نه؟!
برای این مشکل راه حلی معرفی شده‌است به نام Dynamic Proxy که در ادامه به معرفی آن خواهیم پرداخت.


معرفی Dynamic Proxy

Dynamic Proxy یکی از مفاهیم AOP است. به این معنا که توسط آن یک محصور کننده‌ی نامرئی، اطراف یک شیء تشکیل خواهد شد. از این غشای نامرئی عموما جهت مباحث ردیابی اطلاعات، مانند پروکسی‌های Entity framework، همانجایی که تشخیص می‌دهد کدام خاصیت به روز شده‌است یا خیر، استفاده می‌شود و یا این غشای نامرئی کمک می‌کند که در حین دسترسی به خاصیت یا متدی، بتوان منطق خاصی را در این بین تزریق کرد. برای مثال فرآیند تکراری logging سیستم را به این غشای نامرئی منتقل کرد و به این ترتیب می‌توان به کدهای تمیزتری رسید.
یکی دیگر از کاربردهای این محصور کننده یا غشای نامرئی، ساده سازی مباحث Reflection است که نمونه‌ای از آن در پروژه‌ی EntityFramework.Extended بکار رفته‌است.
در اینجا، کار با محصور سازی نمونه‌ای از کلاس مورد نظر با Dynamic Proxy شروع می‌شود. سپس کل عملیات Reflection فوق در همین چند سطر ذیل به نحوی کاملا عادی و طبیعی قابل انجام است:
 // Accessing a private field
dynamic myClassProxy = new DynamicProxy(myClass);
dynamic field1 = myClassProxy._field1;
Console.WriteLine((int)field1);
 
// Accessing a nested private field
dynamic field2 = myClassProxy._nestedClass._field2;
Console.WriteLine((int)field2);
 
// Accessing a private method
dynamic data = myClassProxy.GetData();
Console.WriteLine((string)data);
خروجی Dynamic Proxy از نوع dynamic دات نت 4 است. پس از آن می‌توان در اینجا هر نوع خاصیت یا متد دلخواهی را به شکل dynamic تعریف کرد و سپس به مقادیر آن‌ها دسترسی داشت.

بنابراین با استفاده از Dynamic Proxy فوق می‌توان به دو مهم دست یافت:
 1) ساده سازی و زیبا سازی کدهای کار با Reflection
 2) استفاده‌ی ضمنی از مباحث Fast Reflection. در کتابخانه‌ی Dynamic Proxy معرفی شده، دسترسی به خواص و متدها، توسط کدهای IL بهینه سازی شده‌است و در دفعات آتی کار با آن‌ها، دیگر شاهد سربار بالای Reflection نخواهیم بود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
DynamicProxyTests.zip
مطالب
کار با Docker بر روی ویندوز - قسمت دوم - نصب Docker
پس از آشنایی با مفهوم Containers، در این قسمت قصد داریم برنامه‌ی تقریبا 500 مگابایتی Docker for Windows Installer.exe را نصب کنیم.
 
پیش‌نیازهای نصب Docker بر روی ویندوز

مطابق مستندات آن، برای نصب داکر بر روی ویندوز به حداقل‌های زیر نیاز است:
- استفاده از ویندوز 10 نگارش enterprise، که شماره نگارش آن حداقل 1607 باشد (حداقل Anniversary Update باشد).
- مجازی سازی در BIOS فعال شده باشد.
البته مجازی سازی عموما به صورت پیش‌فرض فعال است. برای بررسی آن، taskmanager ویندوز را اجرا کرده و در برگه‌ی Performance آن، جائیکه مشخصات CPU را نمایش می‌دهد، یک سطر به Virtualization اختصاص دارد که مقدار آن باید enabled باشد (تصویر زیر) و اگر نیست، برای فعال کردن آن باید به تنظیمات BIOS سیستم خود مراجعه کنید:


روش دیگر دریافت این اطلاعات، اجرای دستور systeminfo در خط فرمان، با دسترسی مدیریتی است. در خروجی آن، عبارت «Virtualization Enabled In Firmware» را جستجو کنید که باید مقدار آن yes باشد.

- داشتن CPU با قابلیت SLAT یا Second Level Address Translation.
برای یافتن این موضوع، برنامه‌ی coreinfo را دریافت کرده و آن‌را به صورت coreinfo -v اجرا کنید. خروجی آن سه سطر مرتبط با مجازی سازی را به همراه دارد. اگر قابلیتی موجود نباشد، جلوی آن یک خط تیره و اگر قابلیتی موجود باشد، روبروی آن یک ستاره را مشاهده خواهید کرد.

روش دیگر بررسی آن، اجرای دستور msinfo32 در قسمت run ویندوز و سپس enter است. در قسمت system summary، اطلاعات Second Level Address Translation قابل مشاهده هستند (اگر No باشد، امکان اجرای containerهای لینوکسی را بر روی ویندوز نخواهید داشت):


- داشتن حداقل 4 گیگابایت RAM.
- فعال بودن Hyper-V نیز برای اجرای Linux Containers بر روی ویندوز، ضروری است (نصاب Docker، این‌کار را به صورت خودکار انجام می‌دهد).


دریافت نصاب Docker for Windows

برای دریافت نصاب داکر مخصوص ویندوز، به آدرس زیر مراجعه کنید:
https://store.docker.com/editions/community/docker-ce-desktop-windows
که بلافاصله با تصویر کریه زیر مواجه خواهید شد:


برای رفع این مشکل، می‌توان از روش مطرح شده‌ی در مطلب «یک روش ساده برای دور زدن تحریم‌ها!» استفاده کرد؛ یعنی تنظیم DNS به 178.22.122.100 به صورت زیر:


پس از این تغییر، چون IP قابل مشاهده‌ی سیستم شما توسط سایت داکر تغییر می‌کند، اینبار صفحه‌ی دریافت Docker Community Edition for Windows به صورت زیر ظاهر می‌شود:


همانطور که مشاهده می‌کنید، عنوان کرده‌است که لطفا لاگین کنید تا بتوانید این برنامه را دریافت کنید. به همین جهت بر روی لینک آن کلیک کرده، یک اکانت جدید را در سایت docker ایجاد کنید (با یک ایمیل واقعی که تائیدیه آن‌را دریافت خواهید کرد). پس از آن، با این اکانت جدید به سایت داکر وارد شوید تا لینک دریافت فایل exe نصاب آن‌را دریافت کنید.
در این حالت مرورگر و یا حتی دانلودمنیجر شما بدون مشکل می‌توانند این فایل را دریافت کنند و همان تنظیم DNS فوق، مشکل عدم دسترسی را برطرف می‌کند.


نصب Docker for Windows

پس از اجرای نصاب آن و پایان عملیات نصب (که تنها کافی است در صفحه‌ی ابتدایی آن تیک مربوط به Windows Containers را نیز قرار دهید)، نیاز دارد تا شما را یکبار از سیستم Logout و login کند. پس از ورود به سیستم، تنظیمات ابتدایی آن به صورت خودکار صورت گرفته و در صورت فعال نبودن Hyper-V، پیام زیر را مشاهده خواهید کرد:


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

یک نکته: کاری که در قسمت فعالسازی Hyper-V به صورت خودکار انجام می‌شود، شامل اجرای سه دستور زیر، در کنسول پاور شل، با دسترسی مدیریتی و سپس ری استارت سیستم است:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -Verbose
Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -Verbose
bcdedit /set hypervisorlaunchtype Auto
پس از آن، خط فرمان را باز کرده و با ستفاده از docker CLI نصب شده، دستور docker info را صادر کنید، تا بتوانید مشخصات نگارش نصب شده را مشاهده نمائید.
C:\Users\Vahid>docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 18.06.1-ce
OSType: windows
OSType را در صورتیکه سیستم شما توانمندی‌های سخت افزاری لازم را داشته باشد، می‌توان به Linux نیز تغییر داد.


بررسی تنظیمات سوئیچ کردن بین Linux Containers و Windows Containers

پس از اتمام ری‌استارت‌ها، برای آزمایش فعال بودن Hyper-V، در قسمت Run ویندوز، عبارت Virtmgmt.msc را نوشته و enter کنید. اگر تصویر زیر را مشاهده نمی‌کنید:


یکبار بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینه‌ی switch to Linux containers را انتخاب کنید تا پس از مدتی، آیکن MobyLinuxVM در قسمت virtual machines (تصویر فوق) ظاهر شود.


اگر پس از انتخاب این گزینه، پیام زیر را دریافت کردید:


و یا اگر بر روی این ماشین مجازی کلیک راست کردید و گزینه‌ی Start آن‌را انتخاب کردید و پیام زیر ظاهر شد:


قسمت «پیش‌نیازهای نصب Docker بر روی ویندوز» را با دقت بررسی کنید (خصوصا قسمت BIOS و SLAT). نبود یکی از موارد ذکر شده، سبب بروز این مشکل می‌شود.
برای مثال اجرای دستور coreinfo -v بر روی سیستم من چنین خروجی را به همراه دارد:
E:\>coreinfo -v

AuthenticAMD
Microcode signature: 00000000
HYPERVISOR      -       Hypervisor is present
SVM             *       Supports AMD hardware-assisted virtualization
NP              -       Supports AMD nested page tables (SLAT)
روبروی HYPERVISOR و همچنین SLAT یک - را قرار داده‌است. یعنی این موارد یا پشتیبانی نمی‌شوند و یا در BIOS فعال نشده‌اند.
همانطور که مشاهده می‌کنید، قابلیت SLAT در CPU این سیستم وجود ندارد. به همین جهت نمی‌توان به Linux containers سوئیچ کرد. هرچند windows containers آن کار می‌کند.

روش دیگر مشاهده‌ی این خطا، مراجعه‌ی به event viewer ویندوز است. در قسمت خطاهای سیستم، ممکن است خطای زیر را مشاهده کنید:
Hypervisor launch failed; Second Level Address Translation is required to launch the hypervisor.


آزمایش Docker نصب شده

پس از نصب docker، خط فرمان ویندوز را گشوده و دستور زیر را صادر کنید:
docker run hello-world
اگر از قسمت قبل به‌خاطر داشته باشید، hello-world در اینجا نام یک image است. البته چون این image بر روی سیستم ما موجود نیست، این دستور، ابتدا آن‌را از docker hub دریافت کرده و سپس اجرا می‌کند. بنابراین می‌شد ابتدا دستور pull را صادر کرد و سپس run. اما دستور run قادر است هر دو عمل را با هم انجام دهد.

یک نکته: این image، یک image لینوکسی است. به همین جهت پیش از اجرای این دستور، همانطور که پیشتر نیز عنوان شد، یکبار بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینه‌ی switch to Linux containers را انتخاب کنید. سپس دستور docker run hello-world را اجرا نمائید.

و یا در همین حال دستور docker run -p 80:80 nginx را صادر کنید تا وب سرور لینوکسی nginx را بتوانید تحت ویندوز اجرا کنید. پس از خاتمه‌ی عملیات دریافت و اجرای وب سرور، با توجه به تنظیم  p 80:80-، پورت 80 میزبان (اولین عدد)، به پورت 80 کانتینر نگاشت شده‌است. به همین جهت تنها با اجرای دستور http://localhost، خروجی این وب سرور را می‌توانید در مرورگر سیستم خود مشاهده کنید.
همانطور که مشاهده می‌کنید، با استفاده از داکر، پیش از آنکه بدانیم چگونه باید یک نرم افزار را نصب کرد، می‌توان از آن استفاده کرد!


روش متوقف کردن Containers در حال اجرا

اگر دستور docker ps را در خط فرمان ویندوز اجرا کنید، لیست پروسه‌های اجرا شده‌ی توسط آن قابل مشاهده هستند. در این لیست container id در حال اجرا نیز مشخص است. برای خاتمه‌ی کار آن، تنها کافی است دستور docker stop id را اجرا کنید.
یک نکته: ضرورتی به ذکر کامل id نیست. برای مثال ذکر سه حرف اول آن نیز کفایت می‌کند.


روش اجرای مجدد یک Container

فرض کنید می‌خواهیم سرور nginx را مجددا اجرا کنیم. یک روش آن، اجرای مجدد دستور docker run -p 80:80 nginx است که پیشتر آن‌را انجام دادیم. در این حالت این image تبدیل به container شده و همانند روش‌های متداول نصب نرم افزار، اکنون به عنوان یک نرم افزار نصب شده در دسترس است. برای مشاهده‌ی لیست آن‌ها، دستور docker ps -a را اجرا کنید. این لیست تا این لحظه باید شامل containerهای nginx و hello-world باشد. متوقف کردن یک container، سبب تخریب یا حذف آن نمی‌شود. در این حالت در لیستی که توسط دستور docker ps -a نمایش داده شده‌است، باز هم container idها قابل مشاهده هستند. فقط کافی است برای اجرای یکی از آن‌ها، دستور docker start id را اجرا کرد. به این صورت دیگر نیازی به ذکر دستور کامل docker run با تمام پارامترهای آن نیست. این id نیز همانطور که ذکر شد، می‌تواند سه حرف ابتدایی این id باشد تا حدی که نسبت به سایر idهای موجود، منحصربفرد شناخته شود. یا بجای container id می‌توان container name نمایش داده شده‌ی در این لیست را استفاده کرد.
پس از اجرای nginx توسط دستور docker start id، دو روش برای بررسی در حال اجرا بودن آن وجود دارد:
الف) مرورگر را باز کنیم و آدرس http://localhost را بررسی کنیم.
ب) دستور docker ps را در خط فرمان اجرا کنیم، تا مشخص شود که آیا پروسه‌ی nginx در حال اجرا است یا خیر؟

بنابراین دستور docker ps -a لیست تمام containers در حال اجرا و همچنین متوقف شده را نمایش می‌دهد. اما دستور docker ps تنها لیست containers در حال اجرا را نمایش خواهد داد.


روش حذف Containers از Docker

همانطور که در قسمت قبل نیز بحث شد، معادل نصب نرم افزار در اینجا، ایجاد یک container از یک image دریافتی از docker hub است. روش عکس آن، یعنی تخریب یک container، دقیقا معادل عزل نرم افزار از سیستم، در حالت‌های متداول است. برای اینکار مجددا دستور docker ps -a را اجرا می‌کنیم تا لیست تمام containerهای در حال اجرا و همچنین متوقف شده نمایش داده شوند. لیستی که در اینجا نمایش داده می‌شود، شبیه به لیستی است که در قسمت add/remove programs ویندوز مشاهده می‌کنید. این لیست معادل لیست نرم افزارهای نصب شده‌ی بر روی سیستم است و یا برای مشاهده‌ی لیست imageهای دریافتی از docker hub می‌توان دستور docker images را صادر کرد.
قبل از حذف یک container نیاز است آن‌را متوقف کنیم. برای این منظور از دستور docker stop id استفاده می‌شود. سپس اجرای دستور docker rm id، سبب حذف کامل این container خواهد شد. برای آزمایش آن، مجددا دستور docker ps -a را اجرا کنید.
دستور docker rm چندین id را نیز می‌پذیرد. می‌توان این idها و یا حتی سه حرف ابتدایی آن‌ها را با فاصله در اینجا ذکر کرد. علاوه بر id، ذکر نام containers نیز مجاز است.


روش حذف Imageهای دریافتی از Docker Hub

دستور docker rm، فقط containers را از سیستم حذف می‌کند (نرم افزارهای نصب شده). اما خود imageهای اصلی دریافت شده‌ی از docker hub را حذف نمی‌کند (معادل همان فایل‌های zip دریافت نرم افزار یا برنامه‌های نصاب، در حالت متداول و سنتی نصب نرم افزار). برای آزمایش آن دستور docker images را اجرا کنید. هنوز هم در لیست آن، تمام موارد دریافتی موجود هستند.
برای حذف یک image می‌توان از دستور docker rmi id استفاده کرد (rmi بجای rm). این id نیز در لیست docker images ظاهر می‌شود و ذکر قسمتی از آن، تا حدی که نسبت به سایر idهای لیست شده منحصربفرد باشد، کافی است. در اینجا بجای id، از نام image نیز می‌توان استفاده کرد. همچنین ذکر چندین id و یا نام نیز پس از دستور docker rmi، میسر است.


روش جستجوی imageها در Docker Hub توسط Docker CLI

فرض کنید می‌خواهیم image مربوط به راهنمای Docker را از Docker Hub دریافت کنیم. یک روش آن مراجعه‌ی مستقیم به سایت آن است و استفاده از امکانات جستجوی فراهم شده‌ی در آن سایت. روش دیگر، استفاده از Docker CLI است. اگر دستور docker search docs را در خط فرمان اجرا کنیم، لیست تمام مخازن کدی که در آن‌ها واژه‌ی docs قرار دارد، نمایش داده می‌شود. البته پیش از نصب image آن بهتر است به برگه‌ی tags مخزن کد آن نیز مراجعه کنید تا بتوانید حجم آن‌را نیز مشاهده نمائید که حدود یک گیگابایت است. مخازن docker hub، حاوی imageهای نصاب containerهای متناظر هستند. برای دریافت و اجرای آن می‌توان دستور docker run -p 4000:4000 docs/docker.github.io را اجرا کرد.
پس از دریافت یک گیگابایت مستندات، container آن بر روی پورت 4000 در سیستم ما (http://localhost:4000)، به صورت یک وب سایت استاتیک، قابل دسترسی خواهد بود. به این صورت می‌توان به مستندات کامل داکر به صورت آفلاین دسترسی داشت.


مفهوم Interactive Terminal در Docker

زمانیکه دستور اجرای مستندات آفلاین را صادر می‌کنید، در انتهای آن عنوان می‌کند که وب سایت محلی آن بر روی پورت 4000 قابل دسترسی است. سپس در ذیل آن ذکر شده‌است که اگر ctrl+c را فشار دهید، اجرای آن به پایان می‌رسد. اما عملا اینطور نیست و اگر دستور docker ps را صادر کنید، هنوز container در حال اجرای آن را می‌توان مشاهده کرد.
اما اگر اینبار دستور اجرای docker run را به همراه یک interactive terminal با سوئیچ it و نام docs صادر کنیم:
 docker run -p 4000:4000 -it --name docs docs/docker.github.io
اکنون اگر ctrl+c را فشار دهیم و پس از آن دستور docker ps را صادر کنیم، دیگر در لیست آن، container در حال اجرای docs مشاهده نمی‌شود.
سوئیچ it یا interactive terminal سبب می‌شود تا یک container در foreground، بجای background اجرا شود. به این ترتیب دستور ctrl+c، سبب خاتمه‌ی واقعی پروسه‌ی درحال اجرای در container می‌شود.
روش دیگر خاتمه‌ی این container، استفاده از نام ذکر شده‌است؛ یعنی اجرای دستور docker stop docs.

یک نکته: اگر می‌خواهید از terminal باز شده قطع شوید (مجددا به command prompt باز گردید) اما سبب خاتمه‌ی container آن نشوید، از ترکیب ctrl+p+q استفاده کنید.


اجرای containerهای ویندوزی

در مورد نحوه‌ی سوئیچ بین نوع‌های مختلف containerهای ویندوزی و لینوکسی پیشتر توضیح دادیم. برای این منظور می‌توان بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینه‌ی switch to Windows/Linux containers را انتخاب کرد. باید دقت داشت که پشتیبانی از containerهای ویندوزی، از ویندوز 10، نگارش  1607، یا همان Anniversary Update آن به بعد، به ویژگی‌های ویندوز اضافه شده‌اند که به صورت خودکار توسط docker فعالسازی می‌شوند:



اجرای IIS به عنوان یک Windows Container

تا اینجا imageهای دریافتی، لینوکسی بودند. اگر گزینه‌ی Windows Containers را به روشی که گفته شد، فعال کنید، اینبار با اجرای دستورات docker ps و یا docker images، هیچ خروجی را دریافت نخواهید کرد. از این جهت که کانتینرهای ویندوزی و لینوکسی، به صورت کاملا ایزوله‌ای از هم اجرا و مدیریت می‌شوند. علت آن‌را هم در MobyLinuxVM که پیشتر با اجرای دستور Virtmgmt.msc بررسی کردیم، می‌توان یافت. Containerهای لینوکسی، در داخل MobyLinuxVM اجرا می‌شوند.
در اینجا به عنوان مثال می‌توان image رسمی مربوط به IIS را از docker hub دریافت و به صورت یک کانتینر ویندوزی اجرا کرد. البته پیش از اجرای دستورات آن بهتر است به برگه‌ی tags آن مراجعه کرده و حجم‌های نگارش‌های مختلف آن‌را بررسی کرد. اجرای دستور docker pull microsoft/iis به معنای دریافت tag ای به نام latest است (به حجم 6 گیگابایت!)؛ یعنی با دستور docker pull microsoft/iis:latest یکی است. بنابراین در اینجا بر اساس tagهای مختلف، می‌توان دستور pull متفاوتی را صادر کرد. برای مثال اگر دستور docker pull microsoft/iis:nanoserver را صادر کردید، نگارش مخصوص nano server آن‌را که فقط 449 مگابایت است، دریافت می‌کند. بنابراین از این پس به tagهای هر مخزن docker hub خوب دقت کنید و نگارش مختص به سیستم عامل خود را دریافت نمائید. عدم ذکر tag ای، همواره tag ویژه‌ای را به نام latest، دریافت می‌کند.
با اجرای دستور زیر
 docker run -p 81:80 -d --name iis microsoft/iis:nanoserver
داکر، ابتدا image مخصوص nanoserver آن‌را دریافت و سپس اجرا می‌کند. چون وب سرور است، نیاز به تنظیمات پورت آن‌را داریم. p 81:80- به این معنا است که پورت 80 کانتینر را (پورتی که IIS درون آن بر روی آن اجرا می‌شود)، به پورت 81 بر روی سیستم میزبان (یا همین ویندوز فعلی که داکر را اجرا می‌کند)، نگاشت کن. پارامتر d در اینجا به معنای detach است. یعنی به محض اجرای این دستور، کار اجرای این کانتینر در background انجام شده و سپس به خط فرمان، بازگشت داده می‌شویم. همچنین نامی نیز به این container انتساب داده شده‌است تا ساده‌تر بتوان با آن کار کرد.

یک نکته: مشکلی با اجرای IIS مخصوص نانوسرور بر روی ویندوز 10 به این صورت و توسط داکر نیست. بنابراین پس از اجرای دستور فوق، کار دریافت image و ساخت container و سپس اجرای آن به صورت خودکار انجام شده و بلافاصله به command prompt بازگشت داده می‌شویم (به علت استفاده‌ی از پارامتر d). اکنون اگر دستور docker ps را صادر کنیم، مشاهده می‌کنیم که کانتینر IIS مخصوص نانوسرور، هم اکنون بر روی ویندوز 10 در حال اجرا است و در آدرس http://localhost:81 قابل دسترسی است.

جهت تکمیل این بحث، بهتر است image مخصوص nanoserver را نیز از docker hub دریافت و اجرا کنیم:
 docker run microsoft/windowsservercore
حجم image آن 6GB است.


تنظیمات کارت شبکه‌ی Containers

هنگامیکه پروسه‌ای درون یک container اجرا می‌شود، ایزوله سازی‌های بسیاری نیز در مورد آن اعمال خواهد شد؛ به همین جهت گاهی از اوقات عده‌ای containerها را با ماشین‌های مجازی نیز مقایسه می‌کنند. برای مثال کانتینرها به همراه network adapter خاص خود نیز هستند؛ درست مانند اینکه یک کامپیوتر مجزای از سیستم جاری می‌باشند و اگر این network adapter را ping کنیم، می‌توان به این صورت نیز به آن کانتینر، دسترسی داشته باشیم.
برای یافتن آن، دستور docker inspect iis را صادر می‌کنیم. خروجی آن به همراه یک قسمت network نیز هست که داخل آن یک IP Address قابل مشاهده است. این IP است که مختص و منحصربفرد این container است. در ابتدا برای آزمایش آن، می‌توان آن‌را ping کرد؛ مانند ping 172.27.49.47. همچنین به تمام برنامه‌های داخل این container توسط این IP نیز می‌توان دسترسی یافت. برای مثال فراخوانی http://172.27.49.47:81 در مرورگر، سبب نمایش صفحه‌ی اول IIS می‌شود. البته اگر اینکار را انجام دهیم، کار نمی‌کند. علت اینجا است، نگاشت پورتی را که تعریف کرده‌ایم (پورت 81)، به پورتی در کامپیوتر میزبان است و نه این IP ویژه. برنامه‌ی اصلی IIS در داخل container، به پورت 80 بر روی این آدرس IP گوش فرا می‌دهد. اکنون اگر آدرس http://172.27.49.47:80 را در کامپیوتر میزبان فراخوانی کنیم، کار می‌کند.
بنابراین هرچند containerها به معنای نرم افزارهای از پیش نصب شده‌ی در حال اجرا هستند، اما ... به همراه ایزوله سازی‌های قابل توجهی بر روی کامپیوتر میزبان اجرا می‌شوند؛ درست مانند یک کامپیوتر مجزای از آن.
اشتراک‌ها
دسترسی به پارتیشن های لینوکس در ویندوز

 دسترسی به NTFS به خصوص در لینوکس هایی مثل اوبونتو و مینت راحت‌تر است ولی از ویندوز به پارتیشن‌های ext چطور؟

با استفاده از این برنامه به راحتی می‌توانید دسترسی داشته باشید

من خودم روی سیستم هر دو سیستم عامل ویندوز و اوبونتو را دارم و بارها پیش آمده است که نیاز به فایل هایی دارم که در محیط لینوکس ذخیره کرده ام و با این برنامه به آن‌ها دسترسی پیدا کرده ام

دسترسی به پارتیشن های لینوکس در ویندوز