نظرات مطالب
روشی برای DeSerialize کردن QueryString به یک کلاس
جهت افزودن پشتیبانی از حالت POST کد متد را به شکل زیر تغییر دهید:
 public T GetFromQueryString<T>(RequestType type=RequestType.GET) where T : new()
        {
            var obj = new T();
            var properties = typeof(T).GetProperties();

            var queries =new NameValueCollection();
            if(type==RequestType.GET)
                queries= HttpContext.Current.Request.QueryString;
            else
            {
                queries = HttpContext.Current.Request.Form;
            }

            foreach (var property in properties)
            {
                foreach (Attribute attribute in property.GetCustomAttributes(true))
                {
                    var requestBodyField = attribute as RequestBodyField; 
                    if (requestBodyField == null) continue;

                    //get value of query string
                    var valueAsString = queries[requestBodyField.Field];

                    if (valueAsString == null)
                    {
                        var keys = from key in queries.AllKeys where key.StartsWith(requestBodyField.Field) select key;

                        if(!keys.Any())
                            continue;

                        var collection = new NameValueCollection();

                        foreach (var key in keys)
                        {
                            var openBraketIndex = key.IndexOf("[", StringComparison.Ordinal);
                            var closeBraketIndex = key.IndexOf("]", StringComparison.Ordinal);

                            if (openBraketIndex < 0 || closeBraketIndex < 0)
                                throw new Exception("query string is crupted.");

                            openBraketIndex++;
                            //get key in [...]
                            var fieldName = key.Substring(openBraketIndex, closeBraketIndex - openBraketIndex);
                            collection.Add(fieldName, queries[key]);
                        }
                        property.SetValue(obj, collection, null);
                        continue;
                    }

                    var converter = TypeDescriptor.GetConverter(property.PropertyType);
                    var value = converter.ConvertFrom(valueAsString);

                    if (value == null)
                        continue;

                    property.SetValue(obj, value, null);
                }
            }
            return obj;
        }
بازخوردهای پروژه‌ها
AuditLogs
سلام،
وقتی تابع OnActionExecuted در کلاس Decision.Web.Filters.AuditAttribute رو از حالت کامنت خارج می‌کنم 
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (LogType == AuditLogType.Login)
            {
                AuditLogService.Create(Description, LogType);
            }
            if (LogType != AuditLogType.Serialize) return;
            if (filterContext.Controller.ViewData.ModelState.IsValid)
            {
                AuditLogService.Create(Description, LogType);
            }

            base.OnActionExecuted(filterContext);
        }

موقع login با خطای زیر مواجه می‌شوم؛
البته مشخص است که وقتی هنوز login نشده ایم پس current user هم نداریم! 
 
- این بخش به طور کامل پیاده سازی نشده است؛
این فیلتر (Decision.Web.Filters.AuditAttribute ) برای اکشن‌های زیادی استفاده شده است البته فقط  Description  یا عنوان لاگ در database ذخیره می‌شود و oldvalue و NewValue مقداری ندارند.
این بخش را چطور می‌توان کامل کرد؟  

مطالب
ایجاد چارت سازمانی تحت وب #3
در مطالب قسمت اول و دوم به نحوه ایجاد و تغییر رنگ چارت سازمانی اشاره شد. در این مطلب ، نحوه تغییر فونت‌ها، مکان قرار گرفتن شاخه‌ها و ایجاد لینک در شاخه‌ها ارائه میشود. بدین صورت که در شکل زیر مشاهده مینمائید:

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

با توجه به محدودیت IE در پیاده سازی excanvas  ، در کل کاراکترها متن نود کوتاه میشود. ( اگر کاراکترهای نود ، کاملا پرشونده fit نشوند ، بخشی از کل متن کاراکترهای نود نوشته یا رسم خواهد شد )

پارامترهای تابع ()setFont :

  1. نام فونت . حالت فونت ضخیم bold  یا مورب italic قابل استفاده است.
  2. اندازه فونت در واحد پیکسل
  3. رنگ فونت ( اختیاری )
  4. چیدمان عمودی ( 1 و c یا center برای وسط چین . ( اختیاری )
برای مشاهده از این کدها میتوانید استفاده نمائید:
var o = new orgChart();

o.setColor('#99CC99', '#CCFFCC');
o.setFont('arial', 18);
o.addNode(0, '', '', 'Arial 18', 1);

o.setColor('#CCCC66', '#FFFF99');
o.setFont('arial', 10, '#000000');
o.addNode(11, 0, 'u', 'text will be split if it does not fit. ThisIsAVeryLongWordAndItWillBeClipped. Too many lines will be clipped too.');

o.setFont('arial', 10, '#000000', 0);
o.addNode(12, 0, 'u', 'aligned at top');

o.setFont('arial', 10, '#000000', 1);
o.addNode(13, 0, 'u', 'centered');
o.setColor('#CC4950', '#FF7C80');

o.setFont('times', 16, '#FF0F00');
o.addNode(21, 0, 'l', 'Times 16 red');

o.setFont('times', 16, '#000000');
o.addNode(22, 0, 'l', 'Times 16');

o.setFont('times', 48, '#000000');
o.addNode(23, 0, 'l', 'Times 48 does not fit at all');
o.setColor('#CC9966', '#FFCC99');

o.setFont('jokerman', 12, '#000000');
o.addNode(31, 0, 'r', 'Jokerman (if available)');

o.setFont('bold times', 16, '#000000');
o.addNode(32, 0, 'r', 'bold Times 16');

o.setFont('italic times', 16, '#000000');
o.addNode(33, 0, 'r', 'italic Times 16');

o.setFont('bold italic times', 16, '#000000');
o.addNode(34, 0, 'r', 'bold italic Times 16');

o.setColor('#B5D9EA', '#CFE8EF');
o.setFont('arial', 28, '#000000');
o.addNode(50, '', '', 'Arial 28');
o.setFont('arial', 29);
o.addNode(51, 50, 'u', 'Arial 29');
o.setFont('arial', 30);
o.addNode(52, 51, 'u', 'Arial 30');
o.setFont('arial', 31);
o.addNode(53, 52, 'u', 'Arial 31');
o.setFont('arial', 32);
o.addNode(54, 53, 'u', 'Arial 32');

o.drawChart('c_fonts');

اندازه و مکان :

شما میتوانید اندازه نودها و فضا و offset بین نودها را نیز تنظیم نمائید.این تنظیم بصورت عمومی تاثیر گذار است و تمامی نودها از این تنظیم تبعیت خواهند نمود:


پارامترهای تابع ()setSize:
  1. عرض نودها در واحد پیکسل.
  2. ارتفاع نودها در واحد پیکسل.
  3. فاصله عرضی بین نودهای پدر u-nodes. ( اختیاری ).
  4. فاصله طولی بین نودها ( اختیاری ).
  5. offset عرضی ( فاصله )  از نود چپ و نود راست ( اختیاری ).
var o = new orgChart();

o.setSize(36, 12, 4, 25, 180);

o.setColor('#99CC99', '#CCFFCC');
o.setFont('arial', 18);
o.addNode(0, '', '', 'Root node');
o.setFont('arial', 12);
o.setColor('#CCCC66', '#FFFF99');
o.addNode(11, 0, 'u', 'u-node 1');
o.addNode(12, 0, 'u', 'u-node 2');
o.addNode(13, 0, 'u', 'u-node 3');
o.setColor('#CC4950', '#FF7C80');
o.addNode(21, 0, 'l', 'l-node 1');
o.addNode(22, 0, 'l', 'l-node 2');
o.addNode(23, 0, 'l', 'l-node 3');
o.setColor('#CC9966', '#FFCC99');
o.addNode(31, 0, 'r', 'r-node 1');

o.drawChart('c_size');
پیوندها: LINKS
شما میتوانید به نودها در پارامتر ششم تابع ()addNode آدرس پیوند خود را اضافه نمائید.
در صورت ایجاد پیوند کامل ( مانند : http://www.yourdomain.com ) پیوند در برگه ( tab ) یا یک پنجره جدید ( بسته به تنظیمات مرورگر سمت کاربر ) باز خواهد شد.
اگر نشانگر ماوس ، روی این نوع از نود‌ها قرار بگیرد تغییر شکل به مانند دست ( اشاره گر ) میدهد.



نکته : در این نمونه کد ، هر نود در یک چارت سازمانی جدید دوباره رسم شده اند.در چارت سازمانی قدیمی ، نودها از بین نمی‌روند و همه مسیرهای باقی مانده فعال خواهند ماند.بنابراین اگر reDraw در این نمونه استفاده شود ، چند پیوند در یک نود باز خواهد شد .
اگر بخواهید فقط یک لینک به نودی اختصاص دهید ، یک نود پیوندی بدون پیوند به آن اضافه کنید ( مانند نودها سبز مثال نمونه ).
var o = new orgChart();

o.setColor('#99CC99', '#CCFFCC');
o.setFont('arial', 18);
o.addNode( 0, '', '', 'Searching', 1);
o.addNode(50, '', '', 'Social', 1);
o.addNode(90, '', '', 'Misc.', 1);

o.setColor('#CCCC66', '#FFFF99');
o.setFont('arial', 12);
o.addNode(11, 50, 'u', 'Facebook', 0, 'http://facebook.com');
o.addNode(13, 90, 'u', 'Youtube', 0, 'http://youtube.com');
o.addNode(14, 13, 'l', 'Youtube Music', 0, 'http://youtube.com/music');
o.addNode(15, 13, 'l', 'Youtube Entertainment', 0, 'http://youtube.com/entertainment');

o.setColor('#CC4950', '#FF7C80');
o.addNode(21, 0, 'l', 'Google', 0, 'http://google.com');
o.addNode(22, 0, 'l', 'Bing', 0, 'http://bing.com');

o.addNode('r2', '', '', 'Top of this Page', 0, '#');
o.addNode('', 'r2', 'u', 'Back to the introduction', 0, '/orgchart');

o.drawChart('c_links');

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

مطالب
آموزش QUnit #1
مقدمه:
تست و آزمایش کد برنامه‌ها و وب سایت‌هایمان، بهترین راه کاهش خطا و مشکلات آنها بعد از انتشار است. از جمله روش‌های موجود، تست واحد است که ویژوال استادیو نیز از آن برای پروژه‌های دات نت پشتیبانی می‌کند. با افزایش روز افزون کتابخانه‌های جاوا اسکریپتی و جی کوئری، نیاز به تست کد‌های جاواسکریپتی نیز بیشتر به نظر می‌رسد و بهتر است تست واحد و آزمایش شوند. اما برخلاف کدهای #C و ASP.NET تست کد‌های جاوا اسکریپت، مخصوصا زمانی که به دستکاری عناصر DOM می‌پردازیم و یا رویداد‌های درون صفحه وب را با استفاده از جی کوئری می‌نویسیم، حتی اگر در فایل جداگانه‌ای نوشته شود، این بدان معنی نیست که آماده تست واحد است و ممکن است امکان نوشتن تست وجود نداشته باشد.
بنابراین چه چیزی یک تست واحد است؟ در بهترین حالت توابعی که مقداری را برمی گردادنند، بهترین حالت برای تست واحد است. اما در بیشتر موارد شما نیاز دارید تا تاثیر کد را بر روی عناصر صفحه نیز مشاهد نمایید.
ساخت تست واحد
برای تست پذیری بهتر، توابع جاوا اسکریپت و هر کد دیگری، آن را می‌بایست طوری بنویسید که مقادیر تاثیر گذار در اجرای تابع به عنوان ورودی تابع در نظر گرفته شده باشند و همیشه نتیجه به عنوان خروجی تابع برگردانده شود؛ قطعه کد زیر را در نظر بگیرید:
 function prettyDate(time){
    var date = new Date(time || ""),
      diff = (((new Date()).getTime() - date.getTime()) / 1000),
      day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
      return;
 
    return day_diff == 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor( diff / 60 ) +
          " minutes ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor( diff / 3600 ) +
          " hours ago") ||
      day_diff == 1 && "Yesterday" ||
      day_diff < 7 && day_diff + " days ago" ||
      day_diff < 31 && Math.ceil( day_diff / 7 ) +
        " weeks ago";
  }
تابع pertyDate اختلاف زمان حال را نسبت به زمان ورودی، بصورت یک رشته برمی گرداند. اما در اینجا مقدار زمان حال، در خط سوم، در خود تابع ایجاد شده است و در صورتی که بخواهیم برای چندین مقدار آن را تست کنیم زمان حال متفاوتی در نظر گرفته می‌شود و حداکثر، زمان 31 روز قبل را نمایش داده و در بقیه تاریخ ها undefined را بر می‌گرداند. برای تست واحد، چند تغییر می‌دهیم.
بهینه سازی، مرحله اول:
پارامتری به عنوان مقدار زمان جاری برای تابع در نظر می‌گیریم و تابع را جدا کرده و در یک فایل جداگانه قرار می‌دهیم. فایل prettydate.js بصورت زیر خواهد شد.
function prettyDate(now, time){
  var date = new Date(time || ""),
    diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
    day_diff = Math.floor(diff / 86400);
 
  if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
    return;
 
  return day_diff == 0 && (
      diff < 60 && "just now" ||
      diff < 120 && "1 minute ago" ||
      diff < 3600 && Math.floor( diff / 60 ) +
        " minutes ago" ||
      diff < 7200 && "1 hour ago" ||
      diff < 86400 && Math.floor( diff / 3600 ) +
        " hours ago") ||
    day_diff == 1 && "Yesterday" ||
    day_diff < 7 && day_diff + " days ago" ||
    day_diff < 31 && Math.ceil( day_diff / 7 ) +
      " weeks ago";
}
حال یک تابع برای تست داریم، چند تست واحد واقعی می‌نویسیم
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <script src="prettydate.js"></script>
  <script>
  function test(then, expected) {
    results.total++;
    var result = prettyDate("2013/01/28 22:25:00", then);
    if (result !== expected) {
      results.bad++;
      console.log("Expected " + expected +
        ", but was " + result);
    }
  }
  var results = {
    total: 0,
    bad: 0
  };
  test("2013/01/28 22:24:30", "just now");
  test("2013/01/28 22:23:30", "1 minute ago");
  test("2013/01/28 21:23:30", "1 hour ago");
  test("2013/01/27 22:23:30", "Yesterday");
  test("2013/01/26 22:23:30", "2 days ago");
  test("2012/01/26 22:23:30", undefined);
  console.log("Of " + results.total + " tests, " +
    results.bad + " failed, " +
    (results.total - results.bad) + " passed.");
  </script>
</head>
<body>
 
</body>
</html>
در کد بالا یک تابع بدون استفاده از Qunit برای تست واحد نوشته ایم که با آن تابع prettyDate را تست می‌کند. تابع test مقدار زمان حال و رشته خروجی را گرفته  و آن را با تابع اصلی تست می‌کند در آخر تعداد تست ها، تست‌های شکست خورده و تست  های پاس شده گزارش داده می‌شود.
خروجی می‌تواند مانند زیر باشد:

Of 6 tests, 0 failed, 6 passed.
Expected 2 day ago, but was 2 days ago. 
f 6 tests, 1 failed, 5 passed.

مطالب
ایجاد شناسه ی منحصر به فرد برای هر سیستم
چند روز پیش یک افزونه در nuget نظرم رو به خودش جلب کرد . بعد از دانلود و نصب اون و مقداری کار کردن باهاش جای خودش رو تو دلم باز کرد ولی متاسفانه این افزونه تا 21 روز رایگان بود. توی نت برای پیدا کردن سریال و یا کرکش زیاد گشتم ولی هیچ چیز یافت نشد . شاید به خاطر اینکه از زمان تولیدش زیاد نمیگذره ... در هر حال گذشتن از خیرش برام سخت بود بنابر این به یاد قدیم تصمیم گرفتم خودم دست به کار بشم و release کنمش ... 
بعد از deobfuscate  کردنش سیستم امنیتیش نکات خیلی جالبی داشت که یکیش ایجاد شناسه‌ی منحصر به فرد برای هر سیستم بود . البته شاید باورش سخت باشه ولی برای بخش امنیتش 23 تا کلاس داشت که هر کدوم کلی تابع و ... داشتن که همه هم به هم مرتبط بود . تا به حال ندیده بودم توی هیچ افزونه ای اینقدر روی امنیتش کار بشه و خب همینم باعث شد 5-4 ساعت وقتمو بگیره ...

کد زیر رو از یکی از کلاس هاش استخراج کردم که توسط اون میتونید یک شناسه منحصر به فرد بر اساس مشخصات پردازنده - برد اصلی و ... تولید کنید . فقط در هنگام استفاده توجه داشته باشید به ارجاع‌های برنامه و اینکه باید System.Management رو به reference‌های برنامه اضافه بکنید حتما ...

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Win32;
using System.Management;

namespace ConsoleApplication1
{

    class Program
    {
        private static string GetSystemCode_int1(byte[] byte_0)
        {
            if (byte_0 != null)
            {
                return Convert.ToBase64String((new MD5CryptoServiceProvider()).ComputeHash(byte_0));
            }
            else
            {
                return string.Empty;
            }
        }

        private static string GetSystemCode_int0(string string_2)
        {
            if (!string.IsNullOrEmpty(string_2))
            {
                return GetSystemCode_int1(Encoding.UTF8.GetBytes(string_2));
            }
            else
            {
                return string.Empty;
            }
        }

        public static string GetSystemCode()
        {
            string key = null;
            if (key == null)
            {
                string empty = string.Empty;
                try
                {
                    ManagementClass managementClass = new ManagementClass("win32_processor");
                    ManagementObjectCollection instances = managementClass.GetInstances();
                    foreach (ManagementBaseObject instance in instances)
                    {
                        try
                        {
                            empty = string.Concat(empty, instance.Properties["processorID"].Value.ToString());
                            break;
                        }
                        catch
                        {
                        }
                    }
                }
                catch
                {
                    try
                    {
                        ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher("Select * From Win32_BaseBoard");
                        foreach (ManagementBaseObject managementBaseObject in managementObjectSearcher.Get())
                        {
                            empty = string.Concat(empty, managementBaseObject["SerialNumber"].ToString().Trim());
                        }
                    }
                    catch
                    {
                    }
                }
                try
                {
                    ManagementObject managementObject = new ManagementObject("win32_logicaldisk.deviceid=\"C:\"");
                    managementObject.Get();
                    empty = string.Concat(empty, managementObject["VolumeSerialNumber"].ToString());
                }
                catch
                {
                }
                if (string.IsNullOrWhiteSpace(empty))
                {
                    empty = Environment.MachineName;
                }
                key = GetSystemCode_int0(empty);
            }
            return key;
        }

        static void Main(string[] args)
        {
            Console.WriteLine(GetSystemCode());
            Console.ReadKey();
        }
    }
}
مطالب دوره‌ها
به روز رسانی غیرهمزمان قسمتی از صفحه به کمک jQuery در ASP.NET MVC
یک صفحه شلوغ و سنگین را در نظر بگیرید. برای مثال قرار است ابتدا مطلب خاصی در سایت نمایش یابد و سپس ادامه صفحه که شامل انبوهی از لیست نظرات مرتبط با آن مطلب است به صورت غیرهمزمان و Ajax ایی بدون توقف پردازش صفحه، در فرصتی مناسب از سرور دریافت و به صفحه اضافه گردد (به روز رسانی قسمتی از صفحه در فرصت مناسب). در این حالت چون نمایش اولیه صفحه سریع صورت می‌گیرد، کاربر نهایی آنچنان احساس کند بودن بازکردن صفحات سایت را نخواهد داشت. در ادامه نحوه پیاده سازی این روش را به کمک jQuery Ajax بررسی خواهیم کرد.

مدل و کنترلر برنامه

namespace jQueryMvcSample07.Models
{
    public class BlogPost
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }
}

using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample07.Models;
using jQueryMvcSample07.Security;

namespace jQueryMvcSample07.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش یک منوی ساده در ابتدای کار
        }

        [HttpGet]
        public ActionResult ShowSynchronous()
        {
            var model = getModel();
            return View(model); //نمایش همزمان
        }

        private static BlogPost getModel()
        {
            //شبیه سازی یک عملیات طولانی
            System.Threading.Thread.Sleep(3000);
            var model = new BlogPost
            {
                Title = "عنوان ... ",
                Body = "مطلب... "
            };
            return model;
        }

        [HttpGet]
        public ActionResult ShowAsynchronous()
        {
            return View(); //نمایش ابتدایی صفحه
        }

        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult RenderAsynchronous()
        {
            //دریافت اطلاعات به صورت غیرهمزمان
            var model = getModel();
            return PartialView(viewName: "_Post", model: model);
        }
    }
}
مدل برنامه، بیانگر ساختار اطلاعات مطلبی است که قرار است نمایش یابد.
در کنترلر Home، ابتدا اکشن متد Index آن فراخوانی شده و در این حالت دو لینک زیر نمایش داده می‌شوند:
@{
    ViewBag.Title = "Index";
}
<h2>
    نمایش اطلاعات به صورت همزمان و غیرهمزمان</h2>
<ul>
    <li>
        @Html.ActionLink(linkText: "نمایش همزمان", actionName: "ShowSynchronous", controllerName: "Home")
    </li>
    <li>
        @Html.ActionLink(linkText: "نمایش غیر همزمان", actionName: "ShowAsynchronous", controllerName: "Home")
    </li>
</ul>
لینک اول، به اکشن متد ShowSynchronous اشاره می‌کند و لینک دوم به اکشن متد ShowAsynchronous.
در هر دو صفحه نهایتا از یک Partial View به نام _Post.cshtml برای نمایش اطلاعات استفاده خواهد شد:
@model jQueryMvcSample07.Models.BlogPost
<fieldset>
    <legend>@Model.Title</legend>
    @Model.Body
</fieldset>
زمانیکه کاربر بر روی لینک نمایش همزمان کلیک می‌کند، به صفحه زیر هدایت می‌شود:
@model jQueryMvcSample07.Models.BlogPost
@{
    ViewBag.Title = "ShowSynchronous";
}

<h2>نمایش همزمان</h2>
@{ Html.RenderPartial("_Post", Model); }
این صفحه، یک صفحه متداول است و اطلاعات آن دقیقا در زمان نمایش صفحه اخذ شده و چون در اینجا از یک Sleep عمدی برای تولید اطلاعات استفاده گردیده است، نمایش آن حداقل سه ثانیه طول خواهد کشید.
در حالتیکه کاربر بر روی لینک نمایش غیرهمزمان کلیک می‌کند، صفحه زیر را مشاهده خواهد کرد:
@{
    ViewBag.Title = "ShowAsynchronous";
    var loadInfoUrl = Url.Action(actionName: "RenderAsynchronous", controllerName: "Home");
}
<h2>
    نمایش غیر همزمان</h2>
<div id="info" align="center">
</div>
<div id="progress" align="center" style="display: none">
    <br />
    <img src="@Url.Content("~/Content/images/loadingAnimation.gif")" alt="loading..."  />
</div>
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#progress").css("display", "block");
            $.ajax({
                type: "POST",
                url: '@loadInfoUrl',
                complete: function (xhr, status) {
                    var data = xhr.responseText;
                    if (xhr.status == 403) {
                        window.location = "/login";
                    }
                    else if (status === 'error' || !data || data == "nok") {
                        alert('خطایی رخ داده است');
                    }
                    else {
                        $("#progress").css("display", "none");
                        $("#info").html(data);
                    }
                }
            });
        });
    </script>
}
نمایش ابتدایی این صفحه بسیار سریع است. در ابتدای کار progress bar ایی فعال شده و سپس از طریق jQuery Ajax درخواست دریافت اطلاعات رندر شده اکشن متدی به نام RenderAsynchronous به سرور ارسال می‌شود. چون عملیات Ajax غیرهمزمان است، کاربر نیازی نیست تا رندر شدن کامل صفحه ابتدا صبر کند و سپس کل صفحه به او نمایش داده شود. در اینجا ابتدا صفحه به صورت کامل نمایان شده و سپس درخواستی Ajax ایی به سرور ارسال می‌گردد. در پایان عملیات، Partial View یاد شده رندر گردیده و در div ایی با id مساوی info نمایش داده می‌شود.
به این ترتیب می‌توان حس سریع بودن سایت را زمانیکه قسمتی از صفحه نیاز به زمان بیشتری برای نمایش اطلاعات دارد، به کاربر منتقل کرد.

دریافت پروژه کامل این قسمت
jQueryMvcSample07.zip 
مطالب
ایجاد چارت سازمانی تحت وب #2

در قسمت قبلی درباره ایجاد نمودار سازمانی تحت وب صحبت کردیم .حال اگر بخواهیم آن را با رنگهای مختلف ایجاد کنیم مانند شکل ذیل :



بدین صورت باید عمل کنیم: 

نمودار در داخل canvas رسم شده است. برای اینکه پس زمینه (background) و حاشیه‌های آن (borders) را رنگ آمیزی کنیم، باید تابع رنگ آمیزی را قبل از تابع رسم نمودار صدا بزنیم. میتوانید از کدهای ذیل استفاده نمائید:  

// ایجاد یک پس زمینه رنگی:
var c = document.getElementById("c_canvas");
var cxt = c.getContext("2d");
var gradient = cxt.createLinearGradient(0, 0, 800, 320)
gradient.addColorStop(0, 'Red');
gradient.addColorStop(.5, 'Yellow');
gradient.addColorStop(1, 'Green');
cxt.fillStyle = gradient;
cxt.fillRect(0, 0, 800, 320);

cxt.save();
// این سه خط را فعال کرده تا انتقال نمودار چارت سازمانی را مشاهده نمائید.
//cxt.scale(-1.1, 1.1);
//cxt.translate(-700,-50);
//cxt.rotate(0.2);

var o = new orgChart();

o.addNode(1, '', '', 'Root node');
o.addNode(2, 1, 'u', 'u-node 1');
o.addNode(3, 1, 'u', 'u-node 2');
o.addNode(4, 1, 'u', 'u-node 3');
o.addNode(5, 1, 'l', 'l-node 1');
o.addNode(6, 1, 'l', 'l-node 2');
o.addNode(7, 1, 'r', 'r-node 1');
o.addNode(8, 1, 'r', 'r-node 2');
o.addNode(9, 1, 'r', 'r-node 3');

o.addNode('', '', '', 'Root 2');
o.addNode('', 'Root 2', 'r', 'using');
o.addNode('', 'Root 2', 'r', 'text as id');

o.drawChart('c_canvas', 'center');

cxt.restore();
برای انتقال چارت ، باید کدهای رسم نمودار را توسط تابع انتقال canvas محصور نمائید.

نکته : اگر بخواهید رنگ پس زمینه canvas را کامل پر کند (Fill) باید رسم نمودار را دوبار انجام دهید، در ابتدا تعریف یک canvas با امکان پرشونده در صفحه ، و بعد رسم پس زمینه و بعد رسم دوباره canvas .

رنگها 

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

پارامترهای تابع setColor :

  1. رنگ خطوط حاشیه ( اختیاری )
  2. رنگ پرکننده شاخه ( اختیاری )
  3. رنگ نوشته / عنوان شاخه ( اختیاری )
  4. رنگ خطوط متصل کننده ( اختیاری ، عمومی )

یک پارامتر خالی رنگ ، تنظیمات کنونی را تغییر نخواهد داد. کد زیر را ویرایش نموده و دوباره صفحه خود را بازخوانی نمائید.

var o = new orgChart();

o.setColor('#99CC99', '#CCFFCC', '#000000', '#FF0000');
o.addNode(0, '', '', 'Root node');

o.setColor('#CCCC66', '#FFFF99');
o.addNode(11, 0, 'u', 'u-node 1');

o.setColor('#000000', '#FFFF99');
o.addNode(12, 0, 'u', 'black border');
o.addNode(13, 0, 'u', 'bold black border', 1);

o.setColor('#CC4950', '#FF7C80');
o.addNode(21, 0, 'l', 'l-node 1');
o.addNode(22, 0, 'l', 'l-node 2', 0, 'BLACK', 'RED', 'BLUE');
o.addNode(23, 0, 'l', 'l-node 3');

o.setColor('#CC9966', '#FFCC99');
o.addNode(31, 0, 'r', 'r-node 1');

o.drawChart('c_colors', 'center');
در قسمت بعدی نحوه تغییر فونت‌ها، مکان قرار گرفتن شاخه‌ها و ایجاد لینک در شاخه‌ها ارائه خواهد شد.
مطالب
معرفی کتابخانه‌ی OxyPlot
برای ترسیم نمودار در برنامه‌های WPF، چندین کتابخانه‌ی سورس باز مانند GraphIT ، Sparrow Toolkit ، Dynamic Data Display و ... OxyPlot وجود دارند. در بین این‌ها، کتابخانه‌ی OxyPlot دارای این مزایا است:
- دارای مجوز MIT است. (مجاز هستید از آن در هر نوع برنامه‌ای استفاده کنید)
- cross-platform است. به این معنا که دات نت، WinRT و Xamarin را به خوبی پشتیبانی می‌کند.
- WPF و همچنین WinForms تا Xamarin.Android را پوشش می‌دهد.
- بسته‌های اصلی NuGet آن تا به امروز نزدیک به 40 هزار بار دریافت شده‌اند.
- انجمن فعالی دارد.
- بسیار بسیار غنی است. تا حدی که مرور سطحی مجموعه مثال‌های آن شاید چند ساعت وقت را به خود اختصاص دهد.
- طراحی آن به نحوی است که با الگوی MVVM کاملا سازگاری دارد.
- به صورت متناوبی به روز شده و نگهداری می‌شود.


این برنامه (تصویر فوق) که حاوی مرورگر مثال‌های آن است، در پوشه‌ی Source\Examples\WPF\ExampleBrowser سورس‌های آن قرار دارد.

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


دریافت بسته‌های نیوگت OxyPlot

برای دریافت دو بسته‌ی OxyPlot.Core و OxyPlot.Wpf تنها کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنیم:
 PM> install-package OxyPlot.Wpf


افزودن تعاریف چارت به View

<Window x:Class="OxyPlotWpfTests.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:oxy="http://oxyplot.org/wpf"
        xmlns:oxyPlotWpfTests="clr-namespace:OxyPlotWpfTests"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <oxyPlotWpfTests:MainWindowViewModel x:Key="MainWindowViewModel" />
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource MainWindowViewModel}}">
        <oxy:PlotView Model="{Binding PlotModel}"/>
    </Grid>
</Window>
ابتدا باید فضای نام oxy اضافه شود. پس از آن oxy:PlotView به صفحه اضافه شده و سپس Model آن از ViewModel برنامه تعذیه می‌گردد.


ساختار کلی ViewModel برنامه

کار ViewModel متصل شده به View فوق، مقدار دهی PlotModel است.
public class MainWindowViewModel
{
   public PlotModel PlotModel { get; set; } 

یک نکته‌ی کاربردی

اگر هیچ ایده‌ای نداشتید که این PlotModel را چگونه باید مقدار دهی کرد، به همان برنامه‌ی ExampleBrowser ابتدای مطلب مراجعه کنید.


مثال‌های اجرای شد‌ه‌ی آن یک برگه‌ی نمایشی و یک برگه‌ی Code دارند. خروجی این متدها را اگر به خاصیت PlotModel فوق انتساب دهید ... یک چارت کامل خواهید داشت.


مراحل ساخت یک  PlotModel

ابتدا نیاز است یک وهله‌ی جدید از PlotModel را ایجاد کنیم:

        private void createPlotModel()
        {
            PlotModel = new PlotModel
            {
                Title = "سری خطوط",
                Subtitle = "Pan (right click and drag)/Zoom (Middle click and drag)/Reset (double-click)"
            };
            PlotModel.MouseDown += (sender, args) =>
            {
                if (args.ChangedButton == OxyMouseButton.Left && args.ClickCount == 2)
                {
                    foreach (var axis in PlotModel.Axes)
                        axis.Reset();

                    PlotModel.InvalidatePlot(false);
                }
            };
        }
PlotModel در برگیرنده‌ی محورها، نقاط و تمام ناحیه‌ی چارت است. در اینجا عنوان و زیرعنوان نمودار، مقدار دهی شده‌اند. همچنین در همین ViewModel بدون نیاز به مراجعه به View، می‌توان به رخدادهای مختلف OxyPlot دسترسی داشت. برای مثال می‌خواهیم اگر کاربر دو بار بر روی چارت کلیک کرد، کلیه اعمال zoom و pan آن به حالت اول برگردانده شوند.
برای pan، کافی است دکمه‌ی سمت راست ماوس را نگه داشته و بکشید. به این ترتیب می‌توانید نمودار را بر روی محورهای X و Y حرکت دهید.
برای zoom نیاز است دکمه‌ی وسط ماوس را نگه داشته و بکشید. ناحیه‌ای که در این حالت نمایان می‌گردد، محل بزرگنمایی نهایی خواهد بود.
این دو قابلیت به صورت توکار در OxyPlot قرار دارند و نیازی به کدنویسی برای فعال سازی آن‌ها نیست.



افزودن محورهای X و Y

محور X در مثال ما، از نوع LinearAxis است. بهتر است متغیر آن‌را در سطح کلاس تعریف کرد تا بتوان از آن در سایر قسمت‌های چارت نیز بهره گرفت:
       readonly LinearAxis _xAxis = new LinearAxis();
        private void addXAxis()
        {
            _xAxis.Minimum = 0;
            _xAxis.MaximumPadding = 1;
            _xAxis.MinimumPadding = 1;
            _xAxis.Position = AxisPosition.Bottom;
            _xAxis.Title = "X axis";
            _xAxis.MajorGridlineStyle = LineStyle.Solid;
            _xAxis.MinorGridlineStyle = LineStyle.Dot;
            PlotModel.Axes.Add(_xAxis);
        }
در اینجا مقدار خاصیت Position، مشخص می‌کند که این محور در کجا باید قرار گیرد. اگر مقدار دهی نشود، محور Y را تشکیل خواهد داد.
مقدار دهی GridlineStyleها سبب ایجاد یک Grid خاکستری در نمودار می‌شوند.
در آخر نیاز است این محور به محورهای  PlotModel اضافه شود.

تعریف محور Y نیز به همین نحو است. اگر مقدار خاصیت Position ذکر نشود، این محور در سمت چپ صفحه قرار می‌گیرد:
        readonly LinearAxis _yAxis = new LinearAxis();
        private void addYAxis()
        {
            _yAxis.Minimum = 0;
            _yAxis.Title = "Y axis";
            _yAxis.MaximumPadding = 1;
            _yAxis.MinimumPadding = 1;
            _yAxis.MajorGridlineStyle = LineStyle.Solid;
            _yAxis.MinorGridlineStyle = LineStyle.Dot;
            PlotModel.Axes.Add(_yAxis);
        }


افزودن تعاریف سری‌های خطوط

در تصویر فوق، دو سری خط را ملاحظه می‌کنید. تعاریف پایه سری اول آن به این صورت است:
        readonly LineSeries _lineSeries1 = new LineSeries();
        private void addLineSeries1()
        {
            _lineSeries1.MarkerType = MarkerType.Circle;
            _lineSeries1.StrokeThickness = 2;
            _lineSeries1.MarkerSize = 3;
            _lineSeries1.Title = "Start";
            _lineSeries1.MouseDown += (s, e) =>
            {
                if (e.ChangedButton == OxyMouseButton.Left)
                {
                    PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index);
                    PlotModel.InvalidatePlot(false);
                }
            };
            PlotModel.Series.Add(_lineSeries1);
        }
مقدار خاصیت MarkerType، نحوه‌ی نمایش نقاط اضافه شده را مشخص می‌کند. خاصیت Title، عنوان آن‌را که در کنار صفحه نمایش داده شده، تعیین کرده و در آخر، این سری نیز باید به سری‌های PlotModel اضافه گردد.
هر سری دارای خاصیت MouseDown نیز هست. برای مثال اگر علاقمندید که کلیک کاربر بر روی نقاط مختلف را دریافت کرده و سپس بر این اساس، اطلاعات خاصی را نمایش دهید، می‌توانید از مقدار e.HitTestResult.Index استفاده کنید. در اینجا ایندکس نزدیک‌ترین نقطه به محل کلیک کاربر یافت می‌شود.

تعاریف اولیه سری دوم نیز به همین ترتیب هستند:
        readonly LineSeries _lineSeries2 = new LineSeries();
        private void addLineSeries2()
        {
            _lineSeries2.MarkerType = MarkerType.Circle;
            _lineSeries2.Title = "End";
            _lineSeries2.StrokeThickness = 2;
            _lineSeries2.MarkerSize = 3;
            _lineSeries2.MouseDown += (s, e) =>
            {
                if (e.ChangedButton == OxyMouseButton.Left)
                {
                    PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index);
                    PlotModel.InvalidatePlot(false);
                }
            };
            PlotModel.Series.Add(_lineSeries2);
        }


به روز رسانی دستی OxyPlot

پس از نمایش اولیه OxyPlot، هر تغییری که در اطلاعات آن صورت گیرد، نمایش داده نخواهد شد. برای به روز رسانی آن فقط کافی است متد PlotModel.InvalidatePlot را فراخوانی نمائید. برای نمونه در متدهای فوق، کلیک ماوس، پس از رسم نمودار انجام می‌شود. بنابراین اگر نیاز است زیرعنوان نمودار تغییر کند، باید متد PlotModel.InvalidatePlot نیز فراخوانی گردد.


ایجاد یک تایمر برای افزودن نقاط به صورت پویا

در ادامه می‌خواهیم نقاطی را به صورت پویا به نمودار اضافه کنیم. نمایش یکباره نمودار، نکته‌ی خاصی ندارد. تنها کافی است توسط lineSeries1.Points.Add یک سری DataPoint را اضافه کنید. این نقاط در زمان نمایش View، به یکباره نمایش داده خواهند شد. اما در اینجا ابتدا یک چارت خالی نمایش داده می‌شود و سپس قرار است نقاطی به آن اضافه شوند.
        private int _xMax;
        private int _yMax;
        private bool _haveNewPoints;
        private void addPoints()
        {
            var timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(1)};
            var rnd = new Random();
            var x = 1;
            updateXMax(x);
            timer.Tick += (sender, args) =>
            {
                var y1 = rnd.Next(100);
                updateYMax(y1);
                _lineSeries1.Points.Add(new DataPoint(x, y1));

                var y2 = rnd.Next(100);
                updateYMax(y2);
                _lineSeries2.Points.Add(new DataPoint(x, rnd.Next(y2)));

                x++;

                updateXMax(x);
                _haveNewPoints = true;
            };
            timer.Start();
        }

        private void updateXMax(int value)
        {
            if (value > _xMax)
            {
                _xMax = value;
            }
        }

        private void updateYMax(int value)
        {
            if (value > _yMax)
            {
                _yMax = value;
            }
        }
چند نکته در اینجا حائز اهمیت هستند:
- افزودن نقاط جدید توسط متدهای lineSeries1.Points.Add انجام می‌شوند.
- مقادیر max محورهای x و y را نیز ذخیره می‌کنیم. اگر نقاط برنامه پویا نباشند، OxyPlot به صورت خودکار نمودار را با مقیاس درستی ترسیم می‌کند. اما اگر نقاط پویا باشند، نیاز است حداکثر محورهای x و y را به صورت دستی در آن تنظیم کنیم. به همین جهت متدهای updateXMax و updateYMax در اینجا فراخوانی شده‌اند.
- به روز رسانی ظاهر چارت، توسط متد زیر انجام می‌شود:
        private readonly Stopwatch _stopwatch = new Stopwatch();
        private void updatePlot()
        {
            CompositionTarget.Rendering += (sender, args) =>
            {
                if (_stopwatch.ElapsedMilliseconds > _lastUpdateMilliseconds + 2000 && _haveNewPoints)
                {
                    if (_yMax > 0 && _xMax > 0)
                    {
                        _yAxis.Maximum = _yMax + 3;
                        _xAxis.Maximum = _xMax + 1;
                    }

                    PlotModel.InvalidatePlot(false);

                    _haveNewPoints = false;
                    _lastUpdateMilliseconds = _stopwatch.ElapsedMilliseconds;
                }
            };
        }
کل کاری که در اینجا انجام شده، فراخوانی کنترل شده‌ی PlotModel.InvalidatePlot هر دو ثانیه یکبار است. CompositionTarget.Rendering بر اساس رندر View، عمل کرده و از آن می‌توان برای به روز رسانی نمایشی چارت استفاده کرد. اگر متد PlotModel.InvalidatePlot را دقیقا در زمان افزودن نقاط فراخوانی کنیم به CPU Usage بالایی خواهیم رسید. به همین جهت نیاز است فراخوانی آن کنترل شده و در فواصل زمانی مشخصی باشد. همچنین اگر نقطه‌ای اضافه نشده (بر اساس مقدار haveNewPoints)، به روز رسانی انجام نخواهد شد.
نکته‌ی دیگری که در متد updatePlot فوق درنظر گرفته شده‌است، تغییر مقدار Maximum محورهای x و y بر اساس حداکثرهای نقاط اضافه شده‌است. به این ترتیب نمودار به صورت خودکار جهت نمایش کل اطلاعات، تغییر اندازه خواهد داد.
البته همانطور که عنوان شد، تمام این تهمیدات برای نمایش نمودارهای بلادرنگ است. اگر کار مقدار دهی Points.Add را فقط یکبار در سازنده‌ی ViewModel انجام می‌دهید، نیازی به این نکات نخواهید داشت.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
OxyPlotWpfTests.zip
مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت چهاردهم
در این قسمت قصد داریم به بررسی نحوه‌ی مدیریت خطاها و لاگ کردن آنها بپردازیم. همچنین در ادامه Analytics را در برنامه فعال می‌کنیم تا اطلاعاتی از دستگاه‌های کاربران و ... را به دست بیاوریم (اگر آخرین تغییرات XamApp را Pull/Clone کنید، حاوی تمامی تغییرات زیر است).

در برنامه‌های Native موبایل که شامل Xamarin Forms نیز می‌شود، هر خطایی می‌تواند باعث Crash کردن کل برنامه شود. در Bit Framework، تمامی خطاها مدیریت می‌شوند، تا جلوی بسته شدن برنامه گرفته شود و از این بابت مشکلی متوجه برنامه نمی‌شود و در کدها نیازی به نوشتن Try/Catch هم نیست. خطاها پس از رخ دادن، به کلاس BitExceptionHandler ارسال می‌شوند. شما می‌توانید از این کلاس ارث بری کنید و به شکل زیر حداقل از رخ دادن خطاها در Debug Mode مطلع شوید:
public class XamAppExceptionHandler : BitExceptionHandler
{
     public override void OnExceptionReceived(Exception exp, IDictionary<string, string> properties = null)
      {
#if DEBUG
            System.Diagnostics.Debugger.Break();
#endif
            base.OnExceptionReceived(exp, properties);
      }
}
سپس در ابتدای برنامه‌های Android/iOS/Windows یعنی در فایل‌های MainActivity.cs/AppDelegate.cs/App.xaml.cs کد زیر را برای معرفی کلاس خودتان استفاده کنید:
BitExceptionHandler.Current = new XamAppExceptionHandler();
به صورت پیش فرض، تمامی خطاها را در قسمت System.Diagnostics.Debugger.Break مشاهده خواهید نمود، ولی می‌توانید با باز کردن Exception Settings از منوی Debug > Windows > Exception Settings در ویژوال استودیو و تیک زدن Common Language Runtime Exceptions، تمامی خطاها را در جائیکه رخ می‌دهند، مشاهده کنید که به شما اطلاعات بیشتری نیز می‌دهد.


حال برای لاگ کردن این خطاها، می‌توانید از Microsoft's AppCenter استفاده کنید. استفاده از امکانات App Center رایگان بوده و برای استفاده‌ی در ایران محدودیتی ندارد. ابتدا در سایت مربوطه ثبت نام کنید و سپس سه بار Add New app را بزنید و به نام‌های XamApp_Windows، XamApp_iOS و XamApp_Android سه برنامه را بسازید و برای Android و iOS گزینه‌ی Xamarin را انتخاب و برای ویندوز نیز UWP را انتخاب کنید.

سپس پکیج‌های Microsoft.AppCenter.Crashes و Microsoft.AppCenter.Analytics را بر روی XamApp نصب نموده و کد زیر را در سه فایل AppDelegate.cs/MainActivity.cs/App.xaml.cs برای iOS/Android/Windows کپی کنید:

AppCenter.Start("copy-your-guid-key-for-iOS-Android-Windows-here", typeof(Crashes), typeof(Analytics));

برای هر یک از برنامه‌های Android/iOS/Windows یک Guid متفاوت دارید که در قسمت Getting Started در سایت App Center می‌توانید آنها را مشاهده کنید. هر بار که این کد را کپی می‌کنید، مقدار Guid درست را بگذارید.

برای گزارش کردن خطاهای برنامه، کافی است کد زیر را به XamAppExceptionHandler.cs که در ابتدای این قسمت در موردش صحبت کرده بودیم اضافه کنید:

Crashes.TrackError(exp, properties);

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


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

// code 1...
try
{
      // code 2...
}
catch (Exception ex)
{
                BitExceptionHandler.Current.OnExceptionReceived(ex, new Dictionary<string, string>
                {
                    { "SomeData", "2" }
                });
}
// code 3...

در این کد مثال، فرض کنیم که برخی اوقات در code 2 خطایی رخ می‌دهد که برای ما مهم نیست و می‌خواهیم حتی در صورت رخ دادن خطا، code 3 اجرا شود. توصیه می‌کنیم در این موارد که در برنامه خیلی هم نباید متداول باشد، لااقل خطا را با کمک کد BitExceptionHandler.Current.OnExceptionReceived لاگ کنید و همچنین با داشتن یک Dictionary می‌توانید حتی دیتای بیشتری را نیز به AppCenter فرستاده و در پرتال مربوطه مشاهده کنید.

به صورت کلی بهتر است از این نوع Try/Catch‌ها پرهیز کنید و حتی اگر جایی Catch ای نوشتید، در نهایت دوباره خطا را throw کنید.

try
{
     // some codes...
}
catch
{
      // Do something related to exception...
      // for example, show some alerts to the user.
      throw; // You don't need to call BitExceptionHandler.Current.OnExceptionReceived...
}

در مثال فوق، قصد داریم وقتی خطایی رخ داد، پیامی را به کاربر اطلاع دهیم. در این صورت، پس از نمایش پیام مربوطه، مجددا خطا را throw کنید. در این صورت، نیازی به فراخوانی BitExceptionHandler.Current.OnExceptionReceieved نیز نیست.


البته AppCenter در زمینه پابلیش کردن برنامه و همچنین Push Notification و ... نیز دارای امکاناتی هست که به موضوع این قسمت ارتباطی ندارند.