مطالب
آشنایی با Saltarelle کامپایلر قدرتمند #C به جاوااسکریپت

شاید ساده‌ترین تعریف برای  Saltarelle  این باشد که «کامپایلریست که کد‌های C# را به جاوا اسکریپت تبدیل می‌کند». محاسن زیادی را می‌توان برای اینگونه کامپایلر‌ها نام برد؛ مخصوصا در پروژه‌های سازمانی که نگهداری از کد‌های جاوا اسکریپت بسیار سخت و گاهی خارج از توان است و این شاید مهمترین عامل ظهور ابزارهای جدید از قبیل Typescript باشد.

در هر صورت اگر حوصله و وقت کافی برای تجهیز تیم نرم افزاری، به دانش یک زبان جدید مانند Typescript نباشد، استفاده از توان و دانش تیم تولید، از زبان C# ساده‌ترین راه حل است و اگر ابزاری مطمئن برای استفاده از حداکثر قدرت JavaScript همراه با امکانات نگهداری و توسعه کد‌ها وجود داشته باشد، بی شک Saltarelle یکی از بهترین‌های آنهاست.

قبلا کامپایلر هایی از این دست مانند  Script# وجود داشتند، اما فاقد همه امکانات C# بوده وعملا قدرت کامل C# در کد نویسی وجود نداشت. اما با توجه به ادعای توسعه دهندگان این کامپایلر سورس باز در استفاده‌ی حداکثری از کلیه ویژگی‌های C# 5 و با وجود Library ‌های متعدد می‌توان Saltarelle  را عملا یک کامپایلر موفق در این زمینه دانست.

برای استفاده از Saltarelle در یک برنامه وب ساده باید یک پروژه Console Application به Solution اضافه کرد و پکیج Saltarelle.Compiler را از nuget نصب نمایید. بعد از نصب این پکیج، کلیه Reference ‌ها از پروژه حدف می‌شوند و هر بار Build توسط کامپایلر Saltarelle  انجام می‌شود. البته با اولین Build، مقداری Error را خواهید دید که برای از بین بردنشان نیاز است پکیج Saltarelle.Runtime را نیز در این پروژه نصب نمایید:

PM> Install-Package Saltarelle.Compiler
PM> Install-Package Saltarelle.Runtime

در صورتیکه کماکان Build  نهایی با Error همرا بود، یکبار این پروژه را Unload  و سپس مجددا Load نمایید



UI یک پروژه وب MVC است و Client یک Console Application که پکیج‌های مورد نیاز Saltarelle  روی آن نصب شده است.

در صورتیکه پروژه را Build نماییم و نگاهی به پوشه‌ی Debug بیاندازیم، یک فایل JavaScript همنام پروژه وجود دارد:


برای اینکه بعد از هر بار Build ، فایل اسکریپت به پوشه‌ی مربوطه در پروژه UI منتقل شود کافیست کد زیر را در Post Build  پروژه Client بنویسیم: 

copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)SalratelleSample.UI\Scripts"

اکنون پس از هر بار Build ، فایل اسکریپت مورد نظر در پوشه‌ی Scripts پروژه UI  آپدیت می‌شود:


در ادامه کافیست فایل اسکریپت را به layout اضافه کنیم. 

<script src="~/Scripts/SaltarelleSample.Client.js"></script>

در پوشه‌ی Saltarelle.Runtime در پکیج‌های نصب شده، یک فایل اسکریپت به نام mscorlib.min.js نیز وجود دارد که حاوی اسکریپت‌های مورد نیاز Saltarelle در هنگام اجراست. آن را به پوشه اسکریپت‌های پروژه UI کپی نمایید و سپس به Layout  اضافه کنید. 

<script src="~/Scripts/mscorlib.min.js"></script>
<script src="~/Scripts/SaltarelleSample.Client.js"></script>

حال نوبت به اضافه نمودن library‌های مورد نیازمان است. برای دسترسی به آبجکت هایی از قبیل document, window, element و غیره در جاوااسکریپت می‌توان پکیج Saltarelle.Web را در پروژه‌ی Client نصب نمود و برای دسترسی به اشیاء و فرمانهای jQuery، پکیج Salratelle.jQuery را نصب نمایید. 

> Install-Package Saltarelle.Web
> Install-Package Saltarelle.jQuery

به این library‌ها imported library می‌گویند. در واقع، در زمان کامپایل، برای این library‌ها فایل اسکریپتی تولید نمی‌شود و فقط آبجکت‌های #C هستند که که هنگام کامپایل تبدیل به کدهای ساده اسکریپت می‌شوند که اگر اسکریپت مربوط به آنها به صفحه اضافه نشده باشد، اجرای اسکریپت با خطا مواجه می‌شود.

به طور ساده‌تر وقتی از jQuery library استفاده می‌کنید هیچ فایل اسکریپت اضافه‌ای تولید نمی‌شود، اما باید اسکریپت jQuery به صفحه شما اضافه شده باشد.

<script src="~/Scripts/jquery-1.10.2.min.js"></script>

مثال ما یک اپلیکیشن ساده برای خواندن فید‌های همین سایت است. ابتدا کد‌های سمت سرور را در پروژه UI  می نویسیم.

کلاس‌های مورد نیاز ما برای این فید ریدر: 

public class Feed
    {
        public string FeedId { get; set; }
        public string Title { get; set; }
        public string Address { get; set; }

    }
    public class Item
    {
        public string Title { get; set; }
        public string Link { get; set; }
        public string Description { get; set; }
    }

و یک کلاس برای مدیریت منطق برنامه 

 public class SiteManager
    {
        private static List<Feed> _feeds;
        public static List<Feed> Feeds
        {
            get
            {
                if (_feeds == null)
                    _feeds = CreateSites();
                return _feeds;
            }
        }
        private static List<Feed> CreateSites()
        {
            return new List<Feed>() { 
                new Feed(){
                    FeedId = "1",
                    Title = "آخرین تغییرات سایت",
                    Address = "https://www.dntips.ir/rss.xml"
                },
                 new Feed(){
                    FeedId = "2",
                    Title = "مطالب سایت",
                    Address = "https://www.dntips.ir/feeds/posts"
                },
                 new Feed(){
                    FeedId = "3",
                    Title = "نظرات سایت",
                    Address = "https://www.dntips.ir/feeds/comments"
                },
                 new Feed(){
                    FeedId = "4",
                    Title = "خلاصه اشتراک ها",
                    Address = "https://www.dntips.ir/feed/news"
                },
            };
        }

        public static IEnumerable<Item> GetNews(string id)
        {
            XDocument feedXML = XDocument.Load(Feeds.Find(s=> s.FeedId == id).Address);
            var feeds = from feed in feedXML.Descendants("item")
                        select new Item
                        {
                            Title = feed.Element("title").Value,
                            Link = feed.Element("link").Value,
                            Description = feed.Element("description").Value
                        };
            return feeds;
        }

    }

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

حال دو ApiController برای دریافت داده‌ها ایجاد می‌کنیم

public class FeedController : ApiController  
{
        // GET api/<controller>
        public IEnumerable<Feed> Get()
        {
            return SiteManager.Feeds;
        }
    }

public class ItemsController : ApiController
    {
        // GET api/<controller>/5
        public IEnumerable<Item> Get(string id)
        {
            return SiteManager.GetNews(id);
        }
    }

در View پیش‌فرض که Index از کنترلر Home  است،  یک Html ساده برای فرم  صفحه اضافه می‌کنیم 

<div>
    <div>
        <h2>Feeds</h2>
        <ul id="Feeds">
           
        </ul>
    </div>
    <div>
        <h2>Items</h2>
        <p id="FeedItems">
        </p>
    </div>
   
</div>

در المنت Feeds لیست فید‌ها را قرار می‌دهیم و در FeedItems آیتم‌های مربوط به هر فید. حال به سراغ کد‌های سمت کلاینت می‌رویم و به جای جاوا اسکریپت از Saltarelle استفاده می‌کنیم.

کلاس Program را از پروژه Client باز می‌کنیم و متد Main را به شکل زیر تغییر می‌دهیم:

static void Main()
        {
            jQuery.OnDocumentReady(() => {
                FillFeeds();
            });
        }

بعد از کامپایل شدن، کد #C شارپ بالا به صورت زیر در می‌آید: 

$SaltarelleSample_Client_$Program.$main = function() {
$(function() {
$SaltarelleSample_Client_$Program.$fillFeeds();
});
};
$SaltarelleSample_Client_$Program.$main();

و این همان متد معروف jQuery است که Saltarelle.jQuery برایمان ایجاد کرده است.

متد FillFeeds را به شکل زیر پیاده سازی می‌کنیم

private static void FillFeeds()
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/feed",
                Type = "GET",
                Success = (d,t,r) => {

                    // Fill 
                    var ul = jQuery.Select("#Feeds");
                    jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });
                }
            });
        }

آبجکت jQuery، متدی به نام Ajax دارد که یک شی از کلاس jQueryAjaxOptions را به عنوان پارامتر می‌پذیرد. این کلاس کلیه خصوصیات متد Ajax در jQuery را پیاده سازی می‌کند. نکته شیرین آن توانایی نوشتن lambda برای Delegate هاست.

خاصیت Success یک Delegate است که 3 پارامتر ورودی را می‌پذیرد.

public delegate void AjaxRequestCallback(object data, string textStatus, jQueryXmlHttpRequest request);

data همان مقداریست که api باز می‌گرداند که یک لیست از Feed هاست. برای زیبایی کار، من یک کلاس Feed در پروژه Client اضافه می‌کنم که خصوصیاتی مشترک با کلاس اصلی سمت سرور دارد و مقدار برگشی Ajax را به آن تبدیل می‌کنم.

کلاس Feed و Item

 [PreserveMemberCase()]
    public class Feed
    {
        //[ScriptName("FeedId")]
        public string FeedId;

        //[ScriptName("Title")]
        public string Title;

        //[ScriptName("Address")]
        public string Address;

    }

    [PreserveMemberCase()]
    public class Item
    {
        // [ScriptName("Title")]
        public string Title;

        // [ScriptName("Link")]
        public string Link;

        // [ScriptName("Description")]
        public string Description;
    }
Attrubute‌های زیادی در Saltarelle وجود دارند و از آنجایی که کامپایلر اسم فیلد‌ها را camelCase کامپایل می‌کند من برای جلوگیری از آن از PreserveMemberCase  بر روی هر کلاس استفاده کردم. می‌توانید اسم هر فیلد را سفارشی کامپایل نمایید. 
jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });

به ازای هر آیتمی که در شیء بازگشتی وجود دارد، با استفاد از متد each در jQuery یک li ایجاد می‌کنیم. همان طور که می‌بینید کلیه خواص، به شکل Fluent قابل اضافه شدن می‌باشد. سپس برای li یک رویداد کلیک که در صورت وقوع، متد FillData را با شناسه فید کلیک شده فراخوانی می‌کند و در آخر li را به المنت ul اضافه می‌کنیم.

برای هر کلیک هم مانند مثال بالا api را با شناسه‌ی فید مربوطه فراخوانی کرده و به ازای هر آیتم، یک سطر ایجاد می‌کنیم.

private static void FillData(string p)
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/items/" + p,
                Type = "GET",
                Success = (d, t, r) => {
                    var content = jQuery.Select("#FeedItems");
                    content.Html("");
                    foreach (var item in (List<Item>)d)
                    {
                        var row = jQuery.Select("<div>").AddClass("row").CSS("direction", "rtl");
                        var link = jQuery.Select("<a>").Attribute("href", item.Link).Text(item.Title);
                        row.Append(link);
                        content.Append(row);
                    }
                }
            });
        }
خروجی برنامه به شکل زیر است: 

در این مثال ما از Saltarelle.jQuery برای استفاده از jQuery.js استفاده نمودیم. library‌های متعددی برای Saltarelle  از قبیل  linq,angular,knockout,jQueryUI,nodeJs ایجاد شده و همچنین قابلیت‌های زیادی برای نوشتن imported library‌های سفارشی نیز وجود دارد. 

مطمئنا استفاده از چنین کامپایلرهایی راه حلی سریع برای رهایی از مشکلات متعدد کد نویسی با جاوا اسکریپت در نرم افزارهای بزرگ مقیاس است. اما مقایسه آنها با ابزارهایی از قبیل typescript احتیاج به زمان و تجربه کافی در این زمینه دارد.

مطالب
Closure در JavaScript
در قسمت قبلی درباره علت نیاز به الگوهای طراحی در JavaScript و Function Spaghetti code صحبت شد. در این قسمت Closure در JavaScript مورد بررسی قرار می‌گیرد. 
در JavaScript می‌توان توابع تو در تو نوشت (nested functions) ، زمانی که یک تابع درون تابع دیگر تعریف می‌شود تابع درونی به تمام متغیر‌ها و توابع تابع بیرونی (Parent) دسترسی دارد.
Douglas Crockford برای تعریف Closure می‌گوید : 

an inner function always has access to the vars and parameters of its outer function, even after the outer  function has returned

یک تابع درونی (nested) همیشه به متغیر‌ها و پارامتر‌ها تابع بیرونی دسترسی دارد ، حتی اگر تابع بیرونی مقدار برگردانده باشد. 

تابع زیر را در نظر بگیرید : 
// The getDate() function returns a nested function which refers the 'date' variable defined
// by outer function getDate()
function getDate() {
   var date = new Date();    // This variable stays around even after function returns

   // nested function
   return function () {
      return date.getMilliseconds();
   }
}

  اکنون اگر به صورت زیر تابع getDate فراخوانی شود مشاهده می‌شود که تابع درونی (با کامنت nested function مشخص شده است.) به شیء date دسترسی دارد.
// Once getDate() is executed the variable date should be out of scope and it is, but since
// the inner function
// referenes date, this value is available to the inner function.
var dt = getDate();

alert(dt());
alert(dt());
خروجی هر 2 alert یک مقدار خواهد بود. 
اگر از فردی که به تازگی رو به JavaScript آورده است خواسته شود تابعی بنویسد که میلی ثانیه‌ی زمان جاری را برگداند احتمالا همچین کدی تحویل می‌دهد : 
        function myNonClosure() {
            var date = new Date();
            return date.getMilliseconds();
        }
در کد بالا پس از اجرای myNonClosure متغیر date از بین خواهد رفت ، این مسئله در دنیای JavaScript طبیعی هست.
این مثال را در نظر بگیرید :  
var MyDate = function () {
    var date = new Date();
    var getMilliSeconds = function () {
        return date.getMilliseconds();
    }
}

var dt = new MyDate();

alert(dt.getMilliSeconds());  // This will throw error as getMilliSeconds is not accessible.
در صورت اجرای مثال بالا خطایی با این مضمون دریافت خواهد شد که getMilliSeconds دستیابی پذیر نیست. (کپسوله شده)  برای اینکه آن را دستیابی پذیر کنیم کد را به این صورت تغییر می‌دهیم :
// This is closure 
var MyDate = function () {
    var date = new Date();   // variable stays around even after function returns
    var getMilliSeconds = function () {
        return date.getMilliseconds();
    };
    return {
        getMs : getMilliSeconds
    }
}
آنچه در تابع بالا انجام شده کپسوله سازی همه‌ی منطق کار (منطق کار در اینجا برگرداندن میلی ثانیه زمان جاری می‌باشد) در یک فضای نام به نام MyDate می‌باشد. همچنین فقط متد‌های عمومی در اختیار استفاده کننده این تابع قرار داده شده است. برای استفاده می‌توان بدین صورت عمل کرد : 
var dt = new MyDate();
alert(dt.getMs());  // This should work.
در کد بالا برای توابع و متغیر‌های درونی یک container ایجاد کردیم که باعث جلوگیری از تداخل در نام متغیر‌ها با دیگر کد‌ها خواهد شد . (برای مشاهده‌ی تداخل‌ها به قسمت قبلی  توجه کنید.)
اگر بخواهیم Closure را تشبیه کنیم ، Closure شبیه به کلاس‌ها در C# یا Java هست. 
Closure یک حوزه (scope) برای متغیر‌ها و توابع درونی خودش ایجاد می‌کند.
jQuery بهترین مثال کاربردی برای Closure می‌باشد : 
(function($) {

    // $() is available here

})(jQuery);
در ادامه این مفاهیم بیشتر توضیح داده می‌شودند ، اکنون می‌خواهیم مشکلی که در قسمت قبلی مطرح کردیم به کمک Closure حل کنیم : 
 در آن مثال گفته شد که اگر : 
// file1.js
function saveState(obj) {
    // write code here to saveState of some object
    alert('file1 saveState');
}
// file2.js (remote team or some third party scripts)
function saveState(obj, obj2) {
     // further code...
    alert('file2 saveState");
}
اگر تابعی به نام saveState در 2 فایل مختلف داشته باشیم و این 2 فایل را بدین صورت در برنامه آدرس دهیم : 
<script src="file1.js" type="text/javascript"></script>
<script src="file2.js" type="text/javascript"></script>
تابع saveState در فایل دوم تابع saveState فایل اول را override می‌کند. یک از توابع بالا را به صورت زیر باز نویسی می‌کنیم و منطق کار را کپسوله می‌کنیم : 
function App() {
    var save = function (o) {
        // write code to save state here..
        // you have acces to 'o' here...
        alert(o);
    };

    return {
        saveState: save
    };
}
بدون نگرانی تداخل saveState با بقیه saveState‌ها در هر پلاگین یا فایل دیگری می‌توان از saveState می‌توان اینگونه استفاده کرد : 
var app = new App();

app.saveState({ name: "rajesh"});
برای اطلاعات بیشتر در مورد Closure ها این لینک  را بررسی کنید.
نظرات مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت اول - معرفی
اگر سیستم ما بصورت میکروسرویس نوشته شده باشد و برای ارتباط بین میکروسرویس‌ها از RabbitMQ ویا از GRPC استفاده کرده باشیم آنگاه پیشنهاد شما برای نوشتن تست چیست؟
نظرات مطالب
NHibernate 3 Beginners Guide
سورس این نوع پروژه‌ها برای کسانی که EF یا NH رو نوشتن خوبه. به اون‌ها ایده می‌ده؛ ولی به درد من و شما نمی‌خوره و وقت تلف کردن است. از هر چیزی که GUI داره برای طراحی دوری کنید! ORM ها زمانیکه قسمت عمده کار را از شما مخفی می‌کنند شاید به ظاهر برای یک تازه کار جذاب باشند ولی هر چقدر که جلوتر می‌رن محبور می‌شن به پشت صحنه بیشتر رجوع کنند. NH از روز اول همون پشت صحنه رو نشون می‌ده. صادق هست. ضمنا برای انتخاب هر کتابخانه‌ی نسبتا پایه‌ای باید به این مسایل دقت کنید:
- چه تعداد کتاب چاپ شده در این مورد هست؟
- چه تعداد انجمن برای رفع اشکال آن وجود دارد؟
- چه تعداد بلاگ و سایت در این زمینه مطلب و مقاله منتشر می‌کنند؟
- آخرین تاریخ به روز رسانی آن کی بوده؟
benchmark مهم نیست. گیرم این یکی دو ثانیه زودتر از اون یکی جواب بده. چه اهمیتی داره؟ مهم این است که چند نفر با این کتابخانه کار می‌کنند، چه تعداد منبع دارد و آیا می‌شود در روزهای سخت دنبال پاسخگو گشت؟
NH یک خوبی دیگر هم دارد. در سایت‌های دات نتی مطلب پیدا نکردید، معادل یک به یک جاوا هم دارد.
EF هم داره خوب میشه. این نگارش 4.1 آن خیلی شبیه به Fluent NHibernate شده.
پاسخ به بازخورد‌های پروژه‌ها
طراحی ماژولار با MVC و EF

با سلام و خسته نباشین
ما یک پروژه داریم تحت عنوان ModuleCore , که در یک سولوشن جداگانه است(که از نوع MVC Application است) و قصد داریم این Application رو توسعه بدیم که این ماژول کور شامل فرم‌های نصب ماژول, ایجاد صفحات بصورت مجازی و انتخاب بلوک‌ها بعنوان اجزای صفحات مجازی(بلوک‌ها همان اکشن‌ها و کنترلر‌های هر ماژول می‌باشد) است .
ماژول‌های این پروژه, هرکدام در سولوشن‌های جداگانه هستند.حالا در حال ساخت یک ماژول, بعنوان مثال ماژول نظرسنجی هستیم.ما یک سولوشن جداگانه برای این ماژول ایجاد کردیم (PoolModule). ما یک کنترلی با نام PolController ایجاد کرده ایم و یک اکشنی با نام AddPoll جهت افزودن نظرسنجی توسط مدیریت ایجاد کرده ایم.حال ما پروژه PoolModule را بیلد کرده ایم و ما   فایل دی ال ال PolModule و تمام وابستگی هایش و اسمبلی‌های پیش فرض MVC را در اختیار داریم و تمام ویو‌های مربوط به پروژه PolModule  و تمامی اسکریپت‌ها و استایل‌ها و ... را نیز دارا می‌باشد که مجموع اینها را بسته نصبی ماژول نظرسنجی می‌گوییم .
اطلاعات مربوط به نصب این ماژول را در یک فایل xml ثبت کرده ایم. حال جهت نصب این ماژول در اپلیکیشن(ماژول) ModuleCore فایل xml را به فرم نصبی پاس می‌دهیم(فرم نصبی, شامل یک کنترل فایل آپلود می‌باشد که از طریق آن ما تمام اسمبلی‌ها و فایل‌های مربوط به ماژول نظرسنجی را که در یک فایل فشرده قرار داده ایم را آپلود میکنیم). پس از آپلود فایل xml را خوانده و در یک مسیر مشخص(پوشه Modules/ModuleName/Bin  در پروژه ModuleCore ) ذخیره میکنیم. حال می‌خواهیم در RunTime از اسمبلی PoolModule  در پروژه ModuleCore استفاده کنیم .
چگونه باید اسمبلی‌های ماژول نظرسنجی را به ModuleCore بشناسونیم(می خواهیم به کنترلر‌ها و اکشن‌های ماژول نظرسنجی دسترسی داشته باشیم) بطوریکه اسمبلی‌های هر ماژول(مثلا ماژول نظرسنجی) در bin پروژه ModuleCore  نباشد و اسمبلی‌های هر ماژول در پوشه مخصوص خود ( پوشه Modules/ModuleName/Bin در پروژه ModuleCore ) باشد ؟

ممنون میشم راهنمایی کنین یا اگه لینک منابعی رو معرفی کنین. با تشکر 

نظرات مطالب
بررسی متد های یک طرفه در WCF
زمانی که ارتباط بین سرور و کلاینت به هر دلیلی قطع شود یا به بنا به دلایلی طی انجام عملیات سمت سرور Exception رخ دهد، وضعیت ChannelFactory برای آن سرویس به حالت Faulted  تغییر پیدا می‌کند. در نتیجه دیگر امکان استفاده از این کانال ارتباطی میسر نیست و باید یک کانال ارتباطی جدید تهیه نمایید. اما برای اینکه بعد از متوقف سازی عملیات سمت سرور حافظه مصرفی دوباره بازگردانده شود باید از مفهوم UnitOfWork بهره جست البته با مقداری تغییرات برای همگام سازی با درخواست‌های WCF. روش مورد استفاده من به صورت زیر است(با فرض اینکه از EntityFramework به عنوان ORM استفاده میکنید):
» ابتدا یک Extension برای OperationContext تعریف می‌کنیم(با فرض اینکه IDatabaseContext نماینده کلاس DbContext پروژه است):
private class OperationContainerExtension : IExtension<OperationContext>
        {
            public OperationContainerExtension( IDatabaseContext dbContext, string contextKey )
            {
                this.CurrentDbContext = dbContext;
                this.ContextKey = contextKey;
            }

            public IDatabaseContext CurrentDbContext
            {
                get;
                private set;
            }

            public string ContextKey
            {
                get;
                private set;
            }

            public void Attach( OperationContext owner )
            {
            }

            public void Detach( OperationContext owner )
            {
            }
        }
بعد در هنگام نمونه سازی از UnitOfWork در لایه سرویس Extension بالا به OperationContext جاری اضافه خواهد شد(اگر از IOC Container خاصی استفاده می‌کنید باید کد عملیات وهله سازی کلاس UnitOfWork را با کد‌های زیر مزین کنید):
 if ( OperationContext.Current != null )
            {
                OperationContext.Current.Extensions.Add( new OperationContainerExtension( dbContext , CONTEXTKEY ) );
                OperationContext.Current.OperationCompleted += CurrentOperationContext_OperationCompleted;
                 OperationContext.Current.Channel.Faulted += Channel_Faulted; 
             }
می بینید که برای رویداد OperationCompleted و رویداد Fault در Channel نیز کد‌های Dispose کردن UnitOfWork و همچنین DbContext را قرار دادم. به صورت زیر:
void Channel_Faulted( object sender, EventArgs e )
        {
            IDatabaseContext dbContext = GetDbContext();
            if ( dbContext != null )
            {
                dbContext.Dispose();
                GC.Collect();
            }
        }

        private void CurrentOperationContext_OperationCompleted( object sender, EventArgs e )
        {
            IDatabaseContext dbContext = GetDbContext();
            if ( dbContext != null )
            {
                dbContext.Dispose();
                GC.Collect();
            }
        }
اگر به کد‌های بالا دقت کنید متد GetDbContext نوشته شده برای به دست آوردن DbContext جاری در Session مربوطه است. کد آن نیز به صورت زیر می‌باشد
protected override IDatabaseContext GetDbContext()
        {
            if ( OperationContext.Current != null )
            {
                var operationContainerExtension = OperationContext.Current.Extensions.OfType<OperationContainerExtension>().FirstOrDefault( e => e.ContextKey == CONTEXTKEY );
                if ( operationContainerExtension != null )
                {
                    return operationContainerExtension.CurrentDbContext;
                }
                return staticDbContext;
            }
            else
                return staticDbContext;
        }
نکته آخر هم این است که CONTEXTKEY صرفا یک فیلد از نوع string ولی با مقدار Guid برای به دست آوردن Extension مربوطه می‌باشد و تعریف آن در سازنده کلاس خواهد بود.
private string CONTEXTKEY = Guid.NewGuid().ToString();
در این صورت به طور قطع تمام منابع مورد استفاده سرویس‌های سمت سرور بعد از اتمام عملیات یا حتی وقوع هر خطا آزاد خواهند شد. اما اگر NHibernate را به عنوان ORM ترجیح می‌دهید به جای  IDatabaseContext باید از ISession استفاده نمایید.
نظرات مطالب
سفارشی کردن ASP.NET Identity در MVC 5
سفارشی سازی فیلتر Authorize از ارث بری از AuthorizeAttribute و سپس override کردن متد
public override void OnAuthorization(AuthorizationContext filterContext)
آن شروع می‌شود. در اینجا به اطلاعاتی مانند
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName
filterContext.ActionDescriptor.ActionName
و خیلی موارد دیگر (آدرس صفحه filterContext.HttpContext.Request.Url تا کاربر filterContext.HttpContext.User و غیره) دسترسی خواهید داشت.
سپس باید طراحی جدیدی را بر اساس ControllerName و ActionName پیاده سازی کنید (یک جدول جدید طراحی کنید) تا این اکشن متدها یا کنترلرها امکان انتساب چندین Role متغیر را داشته باشند.
حالا زمانیکه این فیلتر Authorize سفارشی سازی شده بجای فیلتر Autorize اصلی استفاده می‌شود، نام اکشن متد و کنترلر جاری را از filterContext  دریافت می‌کنید. سپس این دو مورد به همراه اطلاعات User جاری، پارامترهایی خواهند شد جهت کوئری گرفتن از بانک اطلاعاتی و جدولی که از آن صحبت شد.
در اینجا هر زمانیکه نیاز بود دسترسی کاربری را قطع کنید فقط کافی است نتیجه‌ی این فیلتر سفارشی را به نحو ذیل بازگردانید:
 filterContext.Result = new HttpStatusCodeResult(403);
بنابراین در قسمت ادمین، یک صفحه‌ی جدید برای ثبت نام کنترلرها و اکشن متدها به همراه نقش‌های پویای آن‌ها خواهید داشت. سپس در این فیلتر Authorize سفارشی، دقیقا مشخص است که اکنون در کدام کنترلر و اکشن متد قرار داریم. بر این اساس (و سایر پارامترهایی که می‌توان از filterContext استخراج کرد) یک کوئری گرفته می‌شود و نقش‌های پویای فیلتر Authorize دریافت می‌شوند. نقش‌های کاربر جاری هم که مشخص هستند. این‌ها را با هم مقایسه می‌کنید و خروجی 403 را درصورت عدم تطابق، تنظیم خواهید کرد.
ضمنا در صفحه‌ی طراحی انتساب نقش‌های متغیر به اکشن متدها یا کنترلرها، امکان یافتن پویای لیست آن‌ها نیز وجود دارد.
مطالب
Blazor 5x - قسمت 23 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 3 - کار با نقش‌های کاربران
در قسمت قبل، روش یکپارچه سازی context مربوط به ASP.NET Core Identity را با یک برنامه‌ی Blazor Server، بررسی کردیم. در این قسمت می‌خواهیم محدود کردن دسترسی‌ها را بر اساس نقش‌های کاربران و همچنین کدنویسی مستقیم، بررسی کنیم.


کار با Authentication State از طریق کدنویسی

فرض کنید در کامپوننت HotelRoomUpsert.razor نمی‌خواهیم دسترسی‌ها را به کمک اعمال ویژگی attribute [Authorize]@ محدود کنیم؛ می‌خواهیم اینکار را از طریق کدنویسی مستقیم انجام دهیم:
// ...

@*@attribute [Authorize]*@


@code
{
    [CascadingParameter] public Task<AuthenticationState> AuthenticationState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationState;
        if (!authenticationState.User.Identity.IsAuthenticated)
        {
            var uri = new Uri(NavigationManager.Uri);
            NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}");
        }
        // ...
- در اینجا در ابتدا اعمال ویژگی Authorize را کامنت کردیم.
- سپس یک پارامتر ویژه را از نوع CascadingParameter، به نام AuthenticationState تعریف کردیم. این خاصیت از طریق کامپوننت CascadingAuthenticationState که در قسمت قبل به فایل BlazorServer.App\App.razor اضافه کردیم، تامین می‌شود.
- در آخر در روال رویدادگردان OnInitializedAsync، بر اساس آن می‌توان به اطلاعات User جاری وارد شده‌ی به سیستم دسترسی یافت و برای مثال اگر اعتبارسنجی نشده بود، با استفاده از NavigationManager، او را به صفحه‌ی لاگین هدایت می‌کنیم.
- در اینجا روش ارسال آدرس صفحه‌ی فعلی را نیز مشاهده می‌کنید. این امر سبب می‌شود تا پس از لاگین، کاربر مجددا به همین صفحه هدایت شود.

authenticationState، امکانات بیشتری را نیز در اختیار ما قرار می‌دهد؛ برای مثال با استفاده از متد ()authenticationState.User.IsInRole آن می‌توان دسترسی به قسمتی را بر اساس نقش‌های خاصی محدود کرد.


ثبت کاربر ادمین Identity

در ادامه می‌خواهیم دسترسی به کامپوننت‌های مختلف را بر اساس نقش‌ها، محدود کنیم. به همین جهت نیاز است تعدادی نقش و یک کاربر ادمین را به بانک اطلاعاتی برنامه اضافه کنیم. برای اینکار به پروژه‌ی BlazorServer.Common مراجعه کرده و تعدادی نقش ثابت را تعریف می‌کنیم:
namespace BlazorServer.Common
{
    public static class ConstantRoles
    {
        public const string Admin = nameof(Admin);
        public const string Customer = nameof(Customer);
        public const string Employee = nameof(Employee);
    }
}
علت قرار دادن این کلاس در پروژه‌ی Common، نیاز به دسترسی به آن در پروژه‌ی اصلی Blazor Server و همچنین در پروژه‌ی سرویس‌های برنامه‌است. فضای نام این کلاس را نیز در فایل imports.razor_ قرار می‌دهیم.

سپس در فایل BlazorServer.App\appsettings.json، مشخصات ابتدایی کاربر ادمین را ثبت می‌کنیم:
{
  "AdminUserSeed": {
    "UserName": "vahid@dntips.ir",
    "Password": "123@456#Pass",
    "Email": "vahid@dntips.ir"
  }
}
جهت دریافت strongly typed این تنظیمات در برنامه، کلاس معادل AdminUserSeed را به پروژه‌ی Models اضافه می‌کنیم:
namespace BlazorServer.Models
{
    public class AdminUserSeed
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }
}
که به صورت زیر در فایل BlazorServer.App\Startup.cs به سیستم تزریق وابستگی‌های برنامه معرفی می‌شود:
namespace BlazorServer.App
{
    public class Startup
    {

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions<AdminUserSeed>().Bind(Configuration.GetSection("AdminUserSeed"));
            // ...
اکنون می‌توان سرویس افزودن نقش‌ها و کاربر ادمین را در پروژه‌ی BlazorServer.Services تکمیل کرد:
using System;
using System.Linq;
using System.Threading.Tasks;
using BlazorServer.Common;
using BlazorServer.DataAccess;
using BlazorServer.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace BlazorServer.Services
{
    public class IdentityDbInitializer : IIdentityDbInitializer
    {
        private readonly ApplicationDbContext _dbContext;
        private readonly UserManager<IdentityUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly IOptions<AdminUserSeed> _adminUserSeedOptions;

        public IdentityDbInitializer(
            ApplicationDbContext dbContext,
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager,
            IOptions<AdminUserSeed> adminUserSeedOptions)
        {
            _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
            _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager));
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _adminUserSeedOptions = adminUserSeedOptions ?? throw new ArgumentNullException(nameof(adminUserSeedOptions));
        }

        public async Task SeedDatabaseWithAdminUserAsync()
        {
            if (_dbContext.Roles.Any(role => role.Name == ConstantRoles.Admin))
            {
                return;
            }

            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Admin));
            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Customer));
            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Employee));

            await _userManager.CreateAsync(
                new IdentityUser
                {
                    UserName = _adminUserSeedOptions.Value.UserName,
                    Email = _adminUserSeedOptions.Value.Email,
                    EmailConfirmed = true
                },
                _adminUserSeedOptions.Value.Password);

            var user = await _dbContext.Users.FirstAsync(u => u.Email == _adminUserSeedOptions.Value.Email);
            await _userManager.AddToRoleAsync(user, ConstantRoles.Admin);
        }
    }
}
این سرویس، با استفاده از دو سرویس توکار UserManager و RoleManager کتابخانه‌ی Identity، ابتدا سه نقش ادمین، مشتری و کارمند را ثبت می‌کند. سپس بر اساس اطلاعات AdminUserSeed تعریف شده، کاربر ادمین را ثبت می‌کند. البته این کاربر در این مرحله، یک کاربر معمولی بیشتر نیست. در مرحله‌ی بعد است که با انتساب نقش ادمین به او، می‌توان کاربر او را بر اساس این نقش ویژه، شناسایی کرد. کلاس‌های IdentityRole و IdentityUser، کلاس‌های پایه‌ی نقش‌ها و کاربران کتابخانه‌ی Identity هستند.

پس از تعریف این سرویس، نیاز است آن‌را به سیستم تزریق وابستگی‌های برنامه اضافه کرد:
namespace BlazorServer.App
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IIdentityDbInitializer, IdentityDbInitializer>();
            // ...
مرحله‌ی آخر، اعمال و اجرای سرویس IIdentityDbInitializer، در زمان آغاز برنامه‌است و محل توصیه شده‌ی آن، در متد Main برنامه‌ی اصلی، پیش از اجرای برنامه‌است. به همین جهت، نیاز است BlazorServer.DataAccess\Utils\MigrationHelpers.cs را به صورت زیر ایجاد کرد:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Polly;

namespace BlazorServer.DataAccess.Utils
{
    public static class MigrationHelpers
    {
        public static void MigrateDbContext<TContext>(
                this IServiceProvider serviceProvider,
                Action<IServiceProvider> postMigrationAction
                ) where TContext : DbContext
        {
            using var scope = serviceProvider.CreateScope();
            var scopedServiceProvider = scope.ServiceProvider;
            var logger = scopedServiceProvider.GetRequiredService<ILogger<TContext>>();
            using var context = scopedServiceProvider.GetService<TContext>();

            logger.LogInformation($"Migrating the DB associated with the context {typeof(TContext).Name}");

            var retry = Policy.Handle<Exception>().WaitAndRetry(new[]
                {
                    TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)
                });

            retry.Execute(() =>
                {
                    context.Database.Migrate();
                    postMigrationAction(scopedServiceProvider);
                });

            logger.LogInformation($"Migrated the DB associated with the context {typeof(TContext).Name}");
        }
    }
}
در مورد این متد و استفاده از Polly جهت تکرار عملیات شکست خورده پیشتر در مطلب «اضافه کردن سعی مجدد به اجرای عملیات Migration در EF Core» بحث شده‌است.
کار متد الحاقی فوق، دریافت یک IServiceProvider است که به سرویس‌های اصلی برنامه اشاره می‌کند. سپس بر اساس آن، یک Scoped ServiceProvider را ایجاد می‌کند تا درون آن بتوان با Context برنامه در طی مدت کوتاهی کار کرد و در پایان آن، سرویس‌های ایجاد شده را Dispose کرد.
در این متد ابتدا Database.Migrate فراخوانی می‌شود تا اگر مرحله‌ای از Migrations برنامه هنوز به بانک اطلاعاتی اعمال نشده، کار اجرا و اعمال آن انجام شود. سپس یک متد سفارشی را از فراخوان دریافت کرده و اجرا می‌کند. برای مثال توسط آن می‌توان IIdentityDbInitializer در فایل BlazorServer.App\Program.cs به صوت زیر فراخوانی کرد:
public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    host.Services.MigrateDbContext<ApplicationDbContext>(
     scopedServiceProvider =>
            scopedServiceProvider.GetRequiredService<IIdentityDbInitializer>()
                                 .SeedDatabaseWithAdminUserAsync()
                                 .GetAwaiter()
                                 .GetResult()
    );
    host.Run();
}
تا اینجا اگر برنامه را اجرا کنیم، سه نقش پیش‌فرض، به بانک اطلاعاتی برنامه اضافه شده‌اند:


و همچنین کاربر پیش‌فرض سیستم را نیز می‌توان مشاهده کرد:


که نقش ادمین و کاربر پیش‌فرض، به این صورت به هم مرتبط شده‌اند (یک رابطه‌ی many-to-many برقرار است):



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

پس از ایجاد کاربر ادمین و تعریف نقش‌های پیش‌فرض، اکنون محدود کردن دسترسی به کامپوننت‌های برنامه بر اساس نقش‌ها، ساده‌است. برای این منظور فقط کافی است لیست نقش‌های مدنظر را که می‌توانند توسط کاما از هم جدا شوند، به ویژگی Authorize کامپوننت‌ها معرفی کرد:
@attribute [Authorize(Roles = ConstantRoles.Admin)]
و یا از طریق کدنویسی به صورت زیر نیز قابل اعمال است:
    protected override async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationState;
        if (!authenticationState.User.Identity.IsAuthenticated ||
            !authenticationState.User.IsInRole(ConstantRoles.Admin))
        {
            var uri = new Uri(NavigationManager.Uri);
            NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}");
        }


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-23.zip
مطالب
الگوریتم‌های داده کاوی در SQL Server Data Tools یا SSDT - قسمت چهارم - الگوریتم‌ Clustering یا خوشه بندی
در قسمت قبل با الگوریتم های Decision trees و Linear Regression آشنا شدیم. در این قسمت به الگوریتم Clustering یا خوشه بندی می‌پردازیم.

مقدمه


تصور کنید شما بچه‌ای هستید که با یک کیسه تیله روی زمین نشسته‌اید. لحظه‌ای که تیله‌ها را از کیسه روی زمین می‌ریزید، متوجه می‌شوید که تیله‌ها، چهار رنگ دارند (آبی، قرمز، سبز و زرد). تیله‌ها را در چهار گروه با توجه به رنگ‌هایشان قرار می‌دهید. اما بعد متوجه می‌شوید که اندازه بعضی از تیله‌ها متوسط و برخی بزرگ و تعدادی هم کوچک هستند. شما تصمیم می‌گیرید که تیله‌های کوچک و متوسط، در کنار یکدیگر و در یک گروه قرار گیرند؛ اما تیله‌های بزرگ را در یک گروه دیگر قرار می‌دهید. چرا که تنها یکی از آن‌ها را باید به هر بازیکن داد. تبریک می‌گویم! شما یک عمل خوشه بندی را انجام دادید.
حال زمانیکه قدری با دقت بیشتری به خوشه بندی خود نگاه می‌کنید، متوجه می‌شوید که برخی از تیله‌ها کریستالی، برخی دیگر سه پر و چهارپر، بعضی از آن‌ها صاف و صیقلی و بعضی دیگر دارای خراش می‌باشند. اینجاست که قدری سردرگم می‌شوید. آیا باید همان گروه بندی ساده براساس رنگ و اندازه را مدنظر قرار دهید، یا بهتر است عوامل دیگری مانند سبک، مواد تشکیل دهنده و وضعیت ظاهری را نیز اضافه کنید؟
خوشه بندی، یک عمل انسانی راحت، طبیعی و حتی می‌شود گفت اتوماتیک برای مواجه شدن با مجموعه ویژگی‌های کوچک می‌باشد. اما همینطور که ویژگی‌ها بیشتر می‌شوند، حل مساله برای انسان خیلی سخت و غیرممکن می‌شود. ذهن یک انسان معمولی، تقریبا قادر به درنظر گرفتن 5 یا 6 بُعد می‌باشد. این درحالی است که مجموعه داده‌های مدرن گاها دارای ده‌ها بعد (اگر نگوییم صدها) می‌باشند.
الگوریتم خوشه بندی مایکروسافت، گروه بندی‌هایی ذاتی را داخل مجموعه داده شما پیدا می‌کند که ممکن است به چشم نیایند. به عبارت دیگر، متغیرهای پنهانی را که به طور دقیق داده‌های شما را خوشه بندی می‌کنند، پیدا می‌نماید. برای مثال فرض کنید که شما جزیی از یک گروه بزرگ مسافران هستید که در بخش نوار نقاله حمل بار در فرودگاه منتظر برداشتن چمدان می‌باشید. متوجه می‌شوید که درصد قابل توجهی از مسافران شلوار کوتاه پوشیده و پوستشان در اثر آفتاب قدری تیره‌تر شده است؛ درحالیکه مابقی مسافران لباس گرم مانند ژاکت و کت به تن دارند. بنابراین به یک حقیقت پی می‌برید. یک گروه از نواحی گرمسیری آمده‌اند و دیگری از یک جای سرد و مرطوب. این همان متغیر پنهان است.

الگوریتم Clustering یا خوشه بندی مایکروسافت  

الگوریتم خوشه بندی مایکروسافت رفتارهای خاصی را در مواجه با نوع ویژگی‌ها از خود نشان می‌دهد. در ارتباط با ستون‌های ورودی (Input) و ورودی-خروجی (Predict) مانند آنچه قبلا گذشت عمل می‌کند. البته با یک تفاوت و آن اینکه ستون‌های ورودی-خروجی در حین پیش بینی قابل انتخاب هستند؛ حال آنکه ستون‌های ورودی اینطور نیستند. ستون‌هایی که فقط خروجی (Predict Only) هستند، در طی فاز خوشه بندی برای آموزش مدل به کار نمی‌روند.
همانطور که قبلا نیز اشاره شد، خوشه بندی، رایج‌ترین عملی است که با این الگوریتم انجام می‌دهند. بنابراین جهت کشف خوشه بندی‌ها در یک مجموعه داده می‌توان این الگوریتم را روی مجموعه داده اعمال کرده و خوشه بندی‌های کشف شده را برچسب زد. بعد از برچسب زدن می‌توان از آن، جهت گزارش گیری و تحلیل داده‌ها استفاده نمود. از آنجا که این الگوریتم سربار پردازشی و حافظه‌ای زیادی دارد، بنابراین در رابطه با مجموعه داده‌های بزرگ (رکوردهای میلیونی و پیچیده) بهتر است که فقط بخش کوچکی از داده را برای آموزش استفاده کرده (که البته کافی و وافی است) و از طریق آن‌ها ویژگی‌های خوشه بندی را کشف کرد.
توسط این الگوریتم می‌توان مدل را تجزیه-تحلیل نمود و نابهنجاری‌ها را نیز تشخیص داد.

محتوای مدل خوشه بندی

درک محتوای مدل خوشه بندی بسیار ساده است. شکل زیر دیاگرام خوشه بندی یا Cluster Diagram می‌باشد. همانطور که در شکل آمده است SSAS در نشان دادن نام هر گره به خوبی عمل نمی‌کند زیرا هر گره توسط Cluster و یک ایندکس نشان داده می‌شود و نام معناداری برای آن در نظر نمی‌گیرد. برای مثال خوشه مربوط به تیله‌های آبی بزرگ سه پر (برای مثال Cluster2، Cluster1 و ....).


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


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

    ممکن است تعدادی ویژگی با احتمال بالا در یک خوشه وجود داشته باشند اما سوال اینجاست که از کجا معلوم که تمام این ویژگی‌ها در خوشه‌های دیگر نیز این احتمال را نداشته باشند؟ برای اینکه متوجه شویم که بیشتر چه ویژگی سبب وجه تمایز این خوشه شده‌است باید به برگه Cluster Discrimination مراجعه کنیم که نمونه‌ای از آن در شکل زیر آمده است.

     در این بخش می‌توان خصوصیات خوشه مدنظر را با خوشه‌های دیگر یا با متمم خوشه (Complement) مقایسه کرد و توسط آن، ویژگی‌هایی را که سبب وجه تمایز این خوشه شده‌اند، مشاهد نمود. توجه به این نکته ضروری است که نوار نشان داده شده در رابطه با هر ویژگی تنها نشان دهنده میزان توجه به آن ویژگی در آن خوشه است و به این معنی نیست که خوشه‌های دیگر عاری از آن ویژگی هستند.

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

خوشه بندی سخت و خوشه بندی نرم

مهمترین فرق بین الگوریتم‌های خوشه بندی، روشی است که الگوریتم‌ها در رابطه با انتساب حالت‌ها، به خوشه‌ها اتخاذ می‌کنند. الگوریتم خوشه بندی مایکروسافت، دو روش مختلف را برای اینکار دارند: K-means و Expectation Maximization. روش اول (شکل سمت چپ) براساس فاصله حالت‌ها نسبت به خوشه‌ها، آن‌ها را نسبت می‌دهد و در پایان مرکز خوشه طوری قرار خواهد گرفت که وسط حالت‌ها باشد. به این تکنیک، خوشه بندی سخت می‌گویند (شکل سمت چپ) زیرا همانطور که در شکل سمت چپ مشخص است هر شیء فقط و فقط در یک خوشه قرار می‌گیرد و هیچ یک از خوشه‌ها با یکدیگر هم پوشانی ندارند. روش دوم (شکل سمت راست) به جای استفاده محض از مقیاس فاصله، از یک مقیاس احتمالی استفاده می‌کند. این روش یک منحنی زنگوله شکل را که دارای میانگین و انحراف معیار است برای هر بُعد درنظر می‌گیرد. چنانچه نقطه‌ای داخل یک منحنی بیفتد با یک احتمال معینی به آن خوشه نسبت داده می‌شود. به دلیل اینکه منحنی‌ها می‌توانند هم پوشانی داشته باشند، بنابراین هر نقطه می‌تواند به چندین خوشه منتسب شود؛ البته با احتمالات مختلف. به این تکنیک، خوشه بندی نرم گفته می‌شود (شکل سمت راست). این تکنیک در شناسایی خوشه‌های پیوسته خیلی موثر است مانند وضعیت تراکم جمعیت مناطق. 


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

یکی از مسایلی که در الگوریتم خوشه بندی وجود دارد این است که جهت به دست آوردن خوشه بندی مناسب، نیاز به تکرار آموزش برروی داده‌ها است. این تکرار در مجموعه داده‌های کوچک، مشکلی ایجاد نمی‌کند، اما در رابطه با مجموعه داده‌های بزرگ این امر امکان پذیر نیست. زیرا کل مجموعه داده داخل رم قرار می‌گیرد و مشکلات کارآیی را ایجاد می‌کند. الگوریتم خوشه بندی مایکروسافت یک چارچوب برای مدرج کردن خوشه بندی را در اختیار ما قرار می‌دهد که با استفاده از آن می‌توان بر این مشکل فایق آمد. این مهم توسط پارامتر Sample_Size مرتفع می‌شود که یکی از پارامترهای این الگوریتم می‌باشد. همانطور که در قسمت قبل نیز گفته شد دسترسی به پارامترهای هر الگوریتم به شکل زیر صورت می‌پذیرد:
مراجعه به برگه mining models ، کلیک بر روی الگوریتم، رفتن به پنجره properties  الگوریتم. حال می‌توان  به بخش Algorithm Parameters رفت و پارامترها را مقداردهی کرد. البته اگر از نظر حافظه رم مشکلی ندارید، می‌توانید مقدار این پارامتر را صفر درنظر بگیرید و با این کار تمام حافظه رم را به پردازش الگوریتم اختصاص بدهید، تا الگوریتم به هر میزانی که نیاز دارد، از حافظه رم استفاده نماید.