نظرات اشتراک‌ها
درس خوندن، ارزشش رو داره؟
گذشته از بحث مدرک محوری که متاسفانه کشور رو به جهت نامناسبی کشونده و باعث خیلی بی عدالتی‌ها و عقب ماندگی‌ها و ... در کشور شده (به خصوص در سیستم هایی که مستقیم یا غیر مستقیم دولتی هستند!)، ماهیت دانشگاه بسیار مهم است.
در دانشگاه چیزهایی مثل نحوه یادگیری، کارگروهی، بالا بردن انگیزه، حس رقابت، حس پیشرفت، اعتماد به نفس و خیلی چیزهای دیگه به طور عادی یا اجباری یاد داده می‌شود که کسی که دانشگاه نرفته قطعا این موارد رو هم تجربه نخواهد کرد بنابراین دیدگاه یک شخص تحصیل کرده از زمین تا آسمان با نمونه دانشگاه نرفته آن تفاوت دارد. (صد البته رتبه دانشگاه نیز در یادگیری و میزان این تجربیات تاثیر فراوان دارد یعنی نمیشه دو تا فارغ التحصیل دانشگاه رو که مثلا یکی در صنعتی اصفهان درس خونده رو با دانشگاه غیر انتفاعی فلان شهرستان(!) مقایسه کرد که از لحاظ وسعت اندازه یک مدرسه هم نیست و هر دو هم کارشناس تربیت می‌کنند.)
اما برای بچه‌های نرم‌افزار به نظرم قضیه حتی مهم‌تر از مابقی رشته‌ها هم هست. یعنی عقیده دارم دانشگاه رفتن برای کسایی که دوست دارند تو حوزه نرم‌افزار کارآفرین، کارشناس، یا هرچیزه دیگه ای بشن خیلی مهمه. چرا؟
(برای مثال) تو دانشگاه اسمبلی به شما یاد می‌دهند به همراه ریزپردازنده و معماری کامپیوتر که با ترکیب نظریه زبان‌ها و کامپایلر دانشجو می‌فهمه از زمانی که یک خط در کامپیوتر به عنوان برنامه در هر زبانی نوشته می‌شه این یک خط چطور توسط کامپایلر از لحاظ نحو، دستور بررسی می‌شه و چطور کد معادل سطح پایین ایجاد می‌شه و کد رو پردازنده چطوری با کمک چه ثبات‌هایی و چه دستوراتی در سطح ماشین اجرا می‌کنه.
یا درسی مثل طراحی الگوریتم‌هاست که انواع و اقسام الگوریتم‌های مختلف که تا الان ارائه شدند بررسی می‌کنه و از لحاظ سرعت و زمان (Order) مورد تحلیل قرار می‌ده که مثلا الگوریتم Quick Sort در چه زمانی یک لیست رو مرتب می‌کنه و Bubble Sort در چه زمانی و یا اینکه ضرب یک ماتریس n*n در حالت تک پردازنده و یا بصورت موازی چطور انجام می‌شه و چقدر زمان نیاز داره؟
یا شبیه سازی پنجره ویندوز در مد گرافیک در زبان C چه کتابخانه‌ها و سخت‌افزارهایی رو درگیر می‌کنه و یا منظور از نرمال سازی در سطح 3NF در مفاهیم پایگاه داده رابطه‌ای چی هست و چطور میشه به اون رو در واقعیت رسید. (که خیلی از مدعیان کار با MS SQL SERVER یا ... هستند که حتی اصول و مفاهیم ساده پایگاه داده رو هم نمی‌دونند.)
اینها چیزهایی هستند که کسانی که دانشگاه نرفتن و صرفا با یادگیری یک زبان برنامه نویسی وارد این حوزه شدند و حتی مدعی هم هستند(!)، نمی‌دانند.
ممکنه فکر کنید خوب دونستن اینها چه فایده‌ای داره؟
زمانی که سرباز بودم از مافوقم پرسیدم با وجود این‌همه سلاح اتوماتیک و سبک و کوچک و جدید چرا ما باید از کلاشینکف صد سال پیش روسی استفاده کنیم؟ جواب این بود که با یادگیری کامل این سلاح می‌تونید تقریبا با همه سلاح‌های موجود کار کنید و کلیات ماجرای همه این سلاح‌ها از همین کلاشینکف و ژ-۳ ارث بری‌می‌کند(!) و یادگیری بقیه با دونستن اطلاعات پایه تنها یادگیری بخش جدیدی که به این سلاح‌ها اضافه شده وگرنه پایه همان است.
شبیه همین جواب را زمانی شنیدم که از استادم پرسیدم یادگیری ساختار پردازنده 80X86 زمانی که الان پردازنده با ساختار چند هسته‌ای توسط اینتل تولید می‌شه چه فایده‌ای داره؟
بنابراین کسی که واقعا این مسائل پایه‌ای رو خوب یاد گرفته باشه توانایی فوق‌العاده‌ای در درک مسائل جدید در آینده خواهد داشت و قطعا محصول بسیار باکیفیت‌تر و بهینه‌تری تولید خواهد کرد و ذهن بسیار خلاق‌تری خواهد داشت و اگر شرایط اجتماعی برایش فراهم باشد موجب افتخار یک کشور نیز خواهد شد.
نکته آخر:
اگر فیلم  The Social Network  رو دیده باشید حتما اون بخشی رو که مارک زاکربرگ وارد کلاس دانشگاه هاروارد میشه و با بی‌حوصلگی تمام سرکلاس به مطالب استاد گوش می‌ده و می‌خواد کلاس رو ترک کنه که استاد ازش یک سوال درسی می‌پرسه (برای مسخره کردنش!) و مارک ایستاده و روی پله‌ها جواب استاد رو میده که هیج ، کمی جلوتر از فکر استاد پیش میره و جواب می‌ده که کلاس هنوز به اون بخش نرسیده و همه مات و مبهوت مونده‌اند...
بله، درسته خیلی از بزرگان این رشته دانشگاه نرفته‌اند و یا ترک تحصیل کرده‌اند ولی علت این بوده که درس‌های ارائه شده در دانشگاه اون‌ها رو سیراب نمی‌کرده و اونها نیاز به چیزی فرای درس‌های ساده دانشگاه داشتند که در دانشگاه هیچ وقت به اون نمی‌رسیدند.
این افراد رو نمیشه با کسانی که در دانشگاه برای پاس کردن فلان درس به هر دری می‌زنند تا به مدرک برسن یا ذهنشون توانایی حل یک معادله ساده درجه ۲ رو نداره که بخوان براش برنامه بنویسن، یکی کرد.
بنابراین امثال جابز و گیتس نوابغ بشری بودند و هستند که تونستند در جامعه‌ای که زمینه براشون مهیا بوده بدون نیاز به دانشگاه موفق بشن و شهرت جهانی پیدا کنند.
موفق باشید.
نظرات مطالب
مقایسه بین حلقه های تکرار (Lambda ForEach و for و foreach)
"این آزمایشات رو اگر در هر سیستم دیگر با هر Config اجرا کنید نتیجه کلی تغییر نخواهد کرد و فقط از نظر زمان اجرا تفاوت خواهیم داشت نه در نتیجه کلی." 
این مطلب لزوما صحیح نیست. یک بنچمارک میتونه تو مجموعه سخت افزارهای مختلف، نتایج کاملا متفاوتی داشته باشه. مثلا سوالی در همین زمینه آقای شهروز جعفری تو StackOverflow پرسیدن که در جوابش دو نفر نتایج متفاوتی ارائه دادن.
معمولا برای بیان نتایج تستهای بنچمارک ابتدا مشخصات سخت افزاری ارائه میشه مخصوصا وقتیکه نتایج دقیق (و نه کلی) نشون داده میشه. مثل همین نتایج دقیق زمانهای اجرای حلقه‌ها.
نکته ای که من درکامنتم اشاره کردم صرفا درباره تست "سرعت اجرای" انواع حلقه‌ها بود که ممکنه با تست کارایی حلقه‌ها در اجرای یک کد خاص فرق داشته باشه.
نکته دیگه هم اینکه نمیدونم که آیا شما از همون متد Console.WriteLine در حلقه‌ها برای اجرای تستون استفاده کردین یا نه. فقط باید بگم که به خاطر مسائل و مشکلات مختلفی که استفاده از این متد به همراه داره، به نظر من بکارگیری اون تو این جور تست‌ها اصلا مناسب نیست و باعث دور شدن زیاد نتایج از واقعیت میشه. مثلا من تست کردم و هر دفعه یه نتیجه‌ای می‌داد که نمیشه بر اساس اون نتیجه‌گیری کرد. 

مورد دیگه ای هم که باید اضافه کنم اینه که بهتر بود شما کد کامل تست خودتون رو هم برای دانلود میذاشتین تا دیگران هم بتونن استفاده کنن. اینجوری خیلی بهتر میشه نتایج مختلف رو با هم مقایسه کرد. این مسئله برای تستای بنچمارک نسبتا رایج هست. مثل کد زیر که من آماده کردم:
static void Main(string[] args)
{
  //Action<int> func = Console.WriteLine;
  Action<int> func = number => number++;
  do
  {
    try
    {
      Console.Write("Iteration: ");
      var iterations = Convert.ToInt32(Console.ReadLine());
      Console.Write("Loop Type (for:0, foreach:1, List.ForEach:2, Array.ForEach:3): ");
      var loopType = Console.ReadLine();
      switch (loopType)
      {
        case "0":
          Console.WriteLine("FOR loop test for {0} iterations", iterations.ToString("0,0"));
          TestFor(iterations, func);
          break;
        case "1":
          Console.WriteLine("FOREACH loop test for {0} iterations", iterations.ToString("0,0"));
          TestForEach(iterations, func);
          break;
        case "2":
          Console.WriteLine("LIST.FOREACH test for {0} iterations", iterations.ToString("0,0"));
          TestListForEach(iterations, func);
          break;
        case "3":
          Console.WriteLine("ARRAY.FOREACH test for {0} iterations", iterations.ToString("0,0"));
          TestArrayForEach(iterations, func);
          break;
      }
    }
    catch (Exception ex)
    {
      Console.WriteLine(ex);
    }
    Console.Write("Continue?(Y/N)");
    Console.WriteLine("");
  } while (Console.ReadKey(true).Key != ConsoleKey.N);

  Console.WriteLine("Press any key to exit");
  Console.ReadKey();
}


static void TestFor(int iterations, Action<int> func)
{
  StartupTest(func);

  var watch = Stopwatch.StartNew();
  for (int i = 0; i < iterations; i++)
  {
    func(i);
  }
  watch.Stop();
  ShowResults("for loop test: ", watch);
}

static void TestForEach(int iterations, Action<int> func)
{
  StartupTest(func);
  var list = Enumerable.Range(0, iterations);

  var watch = Stopwatch.StartNew();
  foreach (var item in list)
  {
    func(item);
  }
  watch.Stop();
  ShowResults("foreach loop test: ", watch);
}

static void TestListForEach(int iterations, Action<int> func)
{
  StartupTest(func);
  var list = Enumerable.Range(0, iterations).ToList();

  var watch = Stopwatch.StartNew();
  list.ForEach(func);
  watch.Stop();
  ShowResults("list.ForEach test: ", watch);
}

static void TestArrayForEach(int iterations, Action<int> func)
{
  StartupTest(func);
  var array = Enumerable.Range(0, iterations).ToArray();

  var watch = Stopwatch.StartNew();
  Array.ForEach(array, func);
  watch.Stop();
  ShowResults("Array.ForEach test: ", watch);
}

static void StartupTest(Action<int> func)
{
  // clean up
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();

  // warm up
  func(0);
}
static void ShowResults(string description, Stopwatch watch)
{
  Console.Write(description);
  Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
قبل از اجرای تست بهتره برنامه رو برای نسخه Release بیلد کنیم. ساده‌ترین روشش در تصویر زیر نشون داده شده:

پس از این تغییر و بیلد پروژه نتایج رو مقایسه میکنیم. نتایج اجرای این تست در همون سیستمی که قبلا تستای StringBuilder و Microbenchmark رو انجام دادم (یعنی لپ تاپ msi GE 620 با Core i7-2630QM) بصورت زیر:

البته نتایج این تستها مطلق نیستن. نکاتی که در کامنت قبلی اشاره کردم از عوامل تاثیرگذار هستن.
موفق باشین.
مطالب
بهبود صفحه‌‌ی بارگذاری اولیه در Blazor WASM
در بار اول اجرای برنامه‌های Blazor WASM، کار دریافت و کش شدن اسمبلی‌های NET Runtime. و برنامه انجام می‌شوند:


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


افزودن یک progress-bar به صفحه‌ی آغازین برنامه‌های Blazor WASM

Blazor امکان دسترسی به چرخه‌ی حیات ابتدایی آن‌را نیز میسر کرده‌است. برای اینکار ابتدا باید به آن گفت که دریافت خودکار تمام موارد مورد نیاز را انجام نده و ما اینکار را خودمان انجام خواهیم داد:
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
برای اینکار باید به تگ اسکریپتی که به blazor.webassembly.js اشاره می‌کند، ویژگی autostart را با مقدار false، افزود. از این پس باید خودمان کار آغاز Blazor را انجام دهیم که در طی دو مرحله، انجام خواهد شد:
الف) تغییر متن Loading پیش‌فرض جهت نمایش یک progress-bar
<body>
<div id="app">
    <div class="d-flex flex-column min-vh-100">
        <div class="d-flex vh-100">
<div class="d-flex w-100 justify-content-center align-self-center">
<div class="d-flex flex-column w-25">
<div>Loading <label id="progressbarLabel"></label></div>
<div class="progress mt-2" style="height: 2em;">
  <div id="progressbar" class="progress-bar progress-bar-striped"></div>  
</div>
</div>
</div>
        </div>
    </div>
</div>
به فایل index.html برنامه مراجعه کرده و بجای loading پیش‌فرض آن، یک چنین طرحی را قرار می‌دهیم که به همراه یک label و یک progressbar در وسط صفحه است:


ب) سپس فایل جدید js/blazorLoader.js را با محتوای زیر اضافه می‌کنیم. در ابتدای این فایل به المان‌های progressbar و progressbarLabel طرح فوق اشاره می‌شود:
(function () {
  let resourceIndex = 0;
  const fetchResponsePromises = [];
  const progressbar = document.getElementById("progressbar");
  const progressbarLabel = document.getElementById("progressbarLabel");
  const loadStart = new Date().getTime();

  if (!isAutostartDisabled()) {
    console.warn(
      "`blazor.webassembly.js` script tag doesn`t have the `autostart=false` attribute."
    );
    return;
  }

  Blazor.start({
    loadBootResource: function (type, filename, defaultUri, integrity) {
      if (type === "dotnetjs") {
        progressbarLabel.innerText = filename;
        return defaultUri;
      }

      const responsePromise = fetch(defaultUri, {
        cache: "no-cache",
        integrity: integrity,
      });
      fetchResponsePromises.push(responsePromise);
      responsePromise.then((response) => {
        if (!progressbar) {
          console.warn("Couldn't find the progressbar element on the page.");
          return;
        }

        if (!progressbarLabel) {
          console.warn(
            "Couldn't find the progressbarLabel element on the page."
          );
          return;
        }

        resourceIndex++;
        const totalResourceCount = fetchResponsePromises.length;
        const percentLoaded = Math.round(
          100 * (resourceIndex / totalResourceCount)
        );
        progressbar.style.width = `${percentLoaded}%`;
        progressbar.innerText = `${percentLoaded} % [${resourceIndex}/${totalResourceCount}]`;
        progressbarLabel.innerText = filename;
        if (percentLoaded >= 100) {
          var span = new Date().getTime() - loadStart;
          console.log(`All done in ${span} ms.`);
        }
      });

      return responsePromise;
    },
  });

  function isAutostartDisabled() {
    var wasmScript = document.querySelector(
      'script[src="_framework/blazor.webassembly.js"]'
    );
    if (!wasmScript) {
      return false;
    }

    var autostart = wasmScript.attributes["autostart"];
    return autostart && autostart.value === "false";
  }
})();
این فایل باید پس از تعریف مدخل blazor.webassembly.js به فایل index.html اضافه شود:
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
<script src="js/blazorLoader.js"></script>
</body>
توضیحات:
- محتوای blazorLoader.js، به صورت خود اجرا شونده تهیه شده‌است.
- متد Blazor.start، کار آغاز دستی Blazor WASM و دریافت فایل‌های مورد نیاز آن‌را انجام می‌دهد.
- خاصیت loadBootResource آن، به تابعی اشاره می‌کند که پیشنیازهای اجرایی Blazor WASM را دریافت می‌کند.
- در متد سفارشی loadBootResource که تهیه کرده‌ایم، responsePromise‌ها را شمارش کرده و بر اساس تعداد کلی آن‌ها و مواردی که دریافت آن‌ها به پایان رسیده‌است، یک progress-bar را تشکیل و نمایش می‌دهیم.
- تابع isAutostartDisabled، بررسی می‌کند که آیا ویژگی autostart مساوی false، به تگ اسکریپت blazor.webassembly.js اضافه شده‌است یا خیر؟


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید:  BlazorWasmLoadingBar.zip
مطالب
#Defensive Code in C - قسمت اول

Defensive Coding به معنی است که شما با انجام یکسری کار‌ها و در نظر گرفتن یکسری زیر ساخت‌ها در توسعه‌ی نرم افزار خود، به اهداف ذیل دست پیدا کنید:

1. Quality (کیفیت)

2. Comprehensible (جامعیت)

3. Predictable  (قابلیت پیش بینی)

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

1. Clean Code

یکی از اهداف Defensive Coding که در ابتدای مقاله بحث شد جامعیت یا Comprehension بود. برای رسید به این هدف از مفهومی به نام Clean Code  استفاده می‌شود. Clean Code علاوه بر این مسئله، در پی ساده کردن ساختار بندی پشتیبانی و کاهش باگ‌های نرم افزار نیز هست. ویژگی‌های Clean Code در بالا با  توجه به شکل ذیل تشریح می‌شوند: 

· Easy to read

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

اگر قابلیت خوانایی یک کد بالا باشد:

§ شما می‌توانید Pattern ‌های موجود در کد خود را که می‌توانید به عنوان نامزدهایی جهت Refactoring  هستند، تشخیص دهید.

§ برنامه نویسان دیگر به راحتی قصد و اهداف ( intent ) شما را از نوشتن یک کد خاص درک خواهند کرد و در طول زمان با خطا‌های زیادی روبرو نمی‌شوند.

§ توسعه‌ی راحت‌تر و در شرایط وجود فشار، ایجاد سریع یک قابلیت جدید در نرم افزار.

· Clear intent

یک کد Clear دارای اهداف روشن و قابل فهمی می‌باشد.

· Simple

پیچیدگی با کم هزینه بودن توسعه‌ی و پشتیبانی تضاد مستقیم دارد. بنابراین سادگی در کدها باید جزو اهداف اصلی قرار بگیرد.

· Minimal

کد باید به گونه‌ای باشد که تنها یک چیز را انجام داده و آن را به درستی انجام دهد. همچنین وابستگی بین اجزای کد باید در کمترین حد ممکن باشند.

· Thoughtful

یک کد Clean  کدی است که ساختار آن متفکرانه طراحی شده باشد. از نحوه‌ی طراحی یک کلاس گرفته تا layering و Tiering پروژه باید کاملا هوشمندانه و با توجه به پارامتر‌های موجود باشند. همچنین خطا‌های خطرناک و استثناء‌ها باید کاملا هندل شوند. 

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

1. Testable Code & Unit Test

یکی دیگر از اهداف Defensive Coding افزایش کیفیت یا Quality می‌باشد که برای رسیدن به این هدف از مفهوم Testable Code & Unit Test استفاده می‌شود. بسیاری از ویژگی‌های Testable Code و Clean Code با هم مشابه می‌باشند. برای مثال Refactor کردن هر متد به متد‌های کوچکتر، تست آن را ساده‌تر خواهند کرد. در نتیجه نوشتن کد‌های Testable ، با نوشتن کد‌های clean شروع می‌شود.

در این قسمت اشاره‌ای به Unit Test شده است؛ اما این مفهوم می‌تواند به یک مفهوم گسترده‌تر به نام  Automated Code testing، تعمیم داده شود. به این دلیل که تست فقط به Unit Testing محدود نمی‌شود و می‌تواند شامل سایر انواع تست‌ها مانند  integration test نیز باشد.

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

 


1. Predictability

یکی دیگر از اهداف Defensive Coding، قابلیت پیش بینی یا Predictability می‌باشد. فرآیند تشخیص و پیش بینی خطا‌ها را Predictability می‌گویند. با درنظر گرفتن امکان وقوع خطاهای مختلف و تصمیم گرفتن در مورد اینکه در هنگام رخ دادن این خطا باید چه کاری صورت بگیرد، می‌توان در رسیدن به این هدف قدم بزرگی برداشت. 

برای رسیدن به این هدف باید اصل Trust but Verify را دنبال کنیم. برای مثال این اصل به ما می‌گوید که در هنگام تعریف متد‌های public باید یکسری موارد را در نظر بگیریم. یک متد باید از یکسری قرارداد‌ها پیروی کند. یک متد قرارداد می‌کند که یکسری پارامتر‌ها را با یک data type خاص به عنوان ورودی دریافت کند. قرارداد می‌کند که یک مقدار خاص با یک data type خاص را به عنوان نوع بازگشتی بازگرداند یا اینکه هیچ مقداری را باز نگرداند و در نهایت یک متد متعهد می‌شود که یکسری Exception ‌تعریف شده و پیش بینی شده را صادر کند. اما برای اینکه مطمئن شویم یک application واقعا قابل پیش بینی است و این اصل را به درستی پیاده سازی کرده است، اعتماد می‌کنیم اما Verify را هم انجام می‌دهیم. برای verify کردن باید پارامترها، دیتا‌های متغیر، مقادیر بازگشتی و استثناء‌ها به گونه‌ای بررسی شوند که مطمئن شویم انتظارت ما را برآورده کرده‌اند. 

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


مطالب
آشنایی با SASS یا Syntactically Awesome Stylesheets و روش نصب و استفاده از آن

استفاده از CSS علاوه بر جذابیت و قابلیت‌های مفید آن، پیچیدگی هایی دارد و کدهای شما معمولا طولانی می‌شود و هرچه کدها طولانی‌تر شوند، مدیریت آن نیز سخت‌تر می‌گردد. اما با استفاده از SASS ، قابلیت هایی به Css اضافه می‌شود که قبلا وجود نداشت، از جمله استفاده از varible ها، نوشتن کدهای تو در تو ( nesting ) و . با استفاده از SASS کدهای CSS کوتاه‌تر شده و در نتیجه سریعتر اجرا شوند. SASS   با CSS3 سازگار است. همچنین امکان مشاهده فایلهای آن (با پسوند .scss ) توسط افزونه Firesass For Firebug وجود دارد.

دو syntax برای SASS وجود دارد: یکی SCSS (Sassy CSS) که شکل توسعه یافته CSS3 می باشدو دیگری که قدیمی‌تر است، Indented syntax می‌باشد که در آن به جای استفاده از براکت، از تورفتگی خطهای کد استفاده می‌شود و همچنین از به جای استفاده از سمی کولن ، باید به خط جدید بروید.

 

قابلیتهای موجود در SASS :

1- Variables

متغیرها امکان ایجاد تغییرات در کدهای CSS را بسیار راحتتر می‌سازند. به عنوان مثال یک متغیر برای یک کد رنگ دلخواه تعریف می‌کنید، از این به بعد به جای استفاده از کد رنگ در کدهای CSS ، از متغیر تعریف شده  برای آن بهره می‌گیرید، به این ترتیب ، چنانچه در آینده نیاز به تغییر این کد رنگ داشته باشید، تنها با تغییر آن در متغیر ، در کل فایل CSS تغییر ایجاد خواهد شد . برای تعریف متغیر ، در ابتدای اسم دلخواه خود از علامت $ استفاده کنید:

$myColor:   #ff0000;
 
body {
    color: $myColor;
}
.box{
Border-color:$myColor;
}

Nesting -2 یا selector ‌های تو در تو:  

می توانید selector ‌ها را مانند کدهای html به صورت hirearchy تعریف کنید:

nav {
     ul {
          list-style: none;
         }
      li { display: inline-block; }
      a {
          text-decoration: none;
         }
}
کدهای بالا بعد از تولید شدن در مرورگر به شکل زیر دیده  می‌شود:

nav ul {
  list-style: none;
}
nav li {
  display: inline-block;
}
nav a {
  text-decoration: none;
}

3- Partials
:

می توانید قطعاتی از کدهای CSS را به صورت Partial SASS تعریف کنید و سپس آن را در فایلهای SASS دیگر استفاده نمایید.همانند Partialview در MVC ، هنگام نام گذاری آن از _ در ابتدای نام استفاده نمایید. فایل partial SASS دارای پسوند   .SCSS می باشد : " "_myPartial.scss

برای استفاده از _myPartial.scss در فایل sass دیگر ، از دایرکتیو @import استفاده کنید:

@import "myPartial"
همچنین می‌توانید نام چندین partial را پشت هم بنویسید:
@import "mypartial1","myPartial2"
نمونه کد:
/*_myPartial1.scss  codes…*/
html,body,ul,ol {
  margin:  0;
  padding: 0;
}
 
/*_myPartial2.scss  codes…*/
 
@import "myPartial1"
 
body, {
 background-color: #efefef;
}
 کدها بعد از تولید در مرورگر:

html, body, ul, ol {
  margin: 0;
  padding: 0;
}
body {
  background-color: #efefef;
  }

4- Mixins :

از آنجایی که استفاده و نوشتن بعضی property ‌های CSS سخت می‌باشد، می‌توانید از روش mixin استفاده کرده و قطعه کدهایی را ایجاد کنید که بتوانید در کدهایتان از آنها بارها و بارها استفاده کنید. به عنوان مثال قطع کدی برای border-radius ایجاد کنید ، (همانطور که میدانید border-radius برای مرورگرهای مختلف ، حالتهای مختلفی تعریف می‌شود.). برای ایجاد mixin ، در ابتدای قطع کد از @mixin استفاده نمایید و برای استفاده ازآن ، از @include استفاده نمایید:  

@mixin cssProperty $yourCustomName{
…
Your css properties…
}

نمونه کد:
ایجاد mixin:

@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
     -moz-border-radius: $radius;
      -ms-border-radius: $radius;
       -o-border-radius: $radius;
          border-radius: $radius;
}

استفاده از mixin:

.box { @include border-radius(10px); }

Extend/Inheritance -5 :

@XETEND به شما این امکان را می‌دهد تا بخشی از Property ‌های یک selector را برای استفاده در selector ‌های دیگر به اشتراک بگذارید:

.message {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}
.success {
  @extend .message;
  border-color: green;
}

کدها بعد از تولید شدن به صورت زیر دیده می‌شوند:

.message, .success {
  border: 1px solid #cccccc;
  padding: 10px;
  color: #333;
}
 
 .success {
  border-color: green;
}

6- Operators :

می توانید از عملگرهای ضرب و تقسیم و جمع و تفریق در کدهای CSS خود استفاده نمایید:

article[role="main"] {
  float: left;
  width: 600px / 960px * 100%;
}

 
نصب SASS :

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

CodeKit , Compass.app , Hammer , Koala , LiveReload , Mixture , Prepros , Prepros Pro , Scout


روش دیگر استفاده از command line می‌باشد:

چنانچه سیستم عامل شما ویندوز می‌باشد، برای استفاده از sass ابتدا باید  rubby را نصب نمایید. سپس در Cmd خط زیر را اجرا کنید:

gem install sass

چنانچه به خطایی برخوردید، ابتدا gem توسط sudo را نصب کنید:

sudo gem install sass

 سپس توسط خط زیر چک کنید که SASS نصب شده است یا خیر:

sass -v
خط فوق، ورژن SASS نصب شده را برای شما می‌گرداند که نشان می‌دهد نصب SASS با موفقیت صورت گرفته است:  
 
Sass 3.2.12 (Media Mark)

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

SassScript :

فایل SASS اسکریپتی برای اجرای یک سری از فانکشنها دارد، از جمله :

- rgb($red, $green, $blue)   /* برای ایجاد کد رنگ rgb */

برای مشاهده لیست کامل این فانکشنها به این لینک مراجعه کنید.

 
 منبع
مطالب
آشنایی با Should Library
 نوشتن Assert در کد‌های تست، وابستگی مستقیم به انتخاب کتابخانه تست دارد. برای مثال:
NUnit:
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
 
namespace TestLibrary
{
   [TestFixture]
   public class MyTest
   {
       [Test]
       public void Test1()
       {
           var expectedValue = 2;
           Assert.That(expectedValue , Is.EqualTo(2));
       }
   }
}

Microsoft UnitTesting :

using Microsoft.VisualStudio.TestTools.UnitTesting ;
 
namespace TestLibrary
{
   [TesClass]
   public class MyTest
   {
       [TestMethod]
       public void Test1()
       {
           var expectedValue = 2;
           Assert.AreEqual (expectedValue , 2);
       }
   }
}
کد‌های Assert نوشته شده در مثال بالا با توجه به فریم ورک مورد استفاده متفاوت است. در حالی که کتابخانه Should، مجموعه ای از Extension Method هاست برای قسمت Assert در UnitTest‌های نوشته شده. با استفاده از این کتابخانه دیگر نیازی به نوشتن Assert به سبک و سیاق فعلی نیست. کدهای Assert بسیار خواناتر و قابل درک خواهند بود و از طرفی وابستگی به سایر کتابخانه‌های تست از بین خواهد رفت.
نکته: مورد استفاده این کتابخانه فقط در قسمت Assert کد‌های تست است و استفاده از سایر کتابخانه‌های جانبی الزامی است.
این کتابخانه به دو صورت مورد استفاده قرار می‌گیرد:
»Standard  که باید از Should.dll استفاده نمایید؛
»Fluent که باید از Should.Fluent.dll استفاده نمایید؛(پیاده سازی همان فریم ورک Should به صورت Static Reflection)

نصب  کتابخانه Should با استفاده از nuget (آخرین نسخه آن در حال حاضر 1.1.20 است ) :
Install-Package Should
  نصب کتابخانه Should.Fluent با استفاده از nuget(آخرین نسخه آن در حال حاضر 1.1.19 است):
Install-Package ShouldFluent

در ابتدا همان مثال قبلی را با این کتابخانه بررسی خواهیم کرد:
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace TestLibrary
{
   [TesClass]
   public class MyTest
   {
       [TestMethod]
       public void Test1()
       {          
           var expectedValue = 2;
           expectedValue.Should().Equal( 2 );
       }
   }
}
در نگاه اول چیز خاصی به چشم نمی‌خورد، اما اگر از این پس قصد داشته باشیم کد‌های تست خود را تحت فریم ورک NUnit پیاده سازی کنیم در قسمت Assert کد‌های خود هیچ گونه خطایی را مشاهده نخواهیم کرد.

مثال:
[TestMethod]        
public void AccountConstructorTest()        
{
     const int expectedBalance = 1000;    
     Account bankAccount = new Account();       
         
     // Assert.IsNotNull(bankAccount, "Account was null.");         
    // Assert.AreEqual(expectedBalance, bankAccount.AccountBalance, "Account balance not mathcing");         
bankAccount.ShouldNotBeNull("Account was null"); bankAccount.AccountBalance.ShouldEqual(expectedBalance, "Account balance not matching"); } 
در مثال بالا ابتدا با استفاده از Ms UnitTesting دو Assert نوشته شده است سپس در خطوط بعدی همان دو شرط با استفاده از کتابخانه Should نوشتم. در ذیل چند مثال از استفاده این کتابخانه (البته نوع Fluent آن) در هنگام کار با رشته ها، آبجکت ها، boolean و Collection‌ها را بررسی خواهیم کرد:

#Should.Fluent
public void Should_fluent_assertions()
{
    object obj = null;
    obj.Should().Be.Null();

    obj = new object();
    obj.Should().Be.OfType(typeof(object));
    obj.Should().Equal(obj);
    obj.Should().Not.Be.Null();
    obj.Should().Not.Be.SameAs(new object());
    obj.Should().Not.Be.OfType<string>();
    obj.Should().Not.Equal("foo");

    obj = "x";
    obj.Should().Not.Be.InRange("y", "z");
    obj.Should().Be.InRange("a", "z");
    obj.Should().Be.SameAs("x");

    "This String".Should().Contain("This");
    "This String".Should().Not.Be.Empty();
    "This String".Should().Not.Contain("foobar");

    false.Should().Be.False();
    true.Should().Be.True();

    var list = new List<object>();
    list.Should().Count.Zero();
    list.Should().Not.Contain.Item(new object());

    var item = new object();
    list.Add(item);
    list.Should().Not.Be.Empty();
    list.Should().Contain.Item(item);
};

#مثال‌های استفاده از متغیر‌های DateTime و Guid
public void Should_fluent_assertions()
{   
    var id = new Guid();
    id.Should().Be.Empty();

    id = Guid.NewGuid();
    id.Should().Not.Be.Empty();

    var date = DateTime.Now;
    date1.Should().Be.Today();

    var str = "";
    str.Should().Be.NullOrEmpty();                

    var one = "1";
    one.Should().Be.ConvertableTo<int>();

    var idString = Guid.NewGuid().ToString();
    idString.Should().Be.ConvertableTo<Guid>();
}


مطالب
AOP با استفاده از Microsoft Unity
چند روز پیش فرصتی پیش آمد تا بتوانم مروری بر مطلب منتشر شده درباره AOP داشته باشم. به حق مطلب مورد نظر، بسیار خوب و مناسب شرح داده شده بود و همانند سایر مقالات جناب نصیری چیزی کم نداشت. اما امروز قصد پیاده سازی یک مثال AOP، با استفاده از Microsoft Unity Application Block را به عنوان IOC Container دارم. اگر شما هم، مانند من از UnityContainer به عنوان IOC Container در پروژه‌های خود استفاده می‌کنید نگران نباشید. این کتابخانه به خوبی از مباحث Interception پشتیبانی می‌کند. در ادامه طی یک مقاله این مورد را با هم بررسی می‌کنیم.
برای دوستانی که با AOP آشنایی ندارند پیشنهاد می‌شود ابتدا مطلب مورد نظر را یک بار مطالعه نمایند.
برای شروع یک پروژه در VS.Net بسازید و ارجاع به اسمبلی‌های زیر را در پروژه فراموش نکنید:
»Microsoft.Practices.EnterpriseLibrary.Common
»Microsoft.Practices.Unity
»Microsoft.Practices.Unity.Configuration
»Microsoft.Practices.Unity.Interception
»Microsoft.Practices.Unity.Interception.Configuration

یک اینترفیس به نام IMyOperation بسازید:
    public interface IMyOperation
    {      
        void DoIt();
    }

کلاسی می‌سازیم که اینترفیس بالا را پیاده سازی نماید:
 public void DoIt()
  {
     Console.WriteLine( "this is main block of code" );
  }
قصد داریم با استفاده از AOP یک سری کد مورد نظر خود(در این مثال کد لاگ کردن عملیات در یک فایل مد نظر است) را به کد‌های متد‌های مورد نظر تزریق کنیم. یعنی با فراخوانی این متد کد‌های لاگ عملیات در یک فایل ذخیره شود بدون تکرار یا فراخوانی دستی متد لاگ.
ابتدا یک کلاس برای لاگ عملیات می‌سازیم:
public class Logger
    {
        const string path = @"D:\Log.txt";

        public static void WriteToFile( string methodName )
        {
            object lockObject = new object();
            if ( !File.Exists( path ) )
            {
                File.Create( path );
            }
            lock ( lockObject )
            {
                using ( TextWriter writer = new StreamWriter( path , true ) )
                {
                    writer.WriteLine( string.Format( "{0} at {1}" , methodName , DateTime.Now ) );                
                }
            }
        }
    }
حال نیاز به یک Handler برای مدیریت فراخوانی کد‌های تزریق شده داریم. برای این کار یک کلاس می‌سازیم که اینترفیس ICallHandler را پیاده سازی نماید.
public class LogHandler : ICallHandler
    {
        public IMethodReturn Invoke( IMethodInvocation input , GetNextHandlerDelegate getNext )
        {
            Logger.WriteToFile( input.MethodBase.Name );

            var methodReturn = getNext()( input , getNext );         

            return methodReturn;
        }

        public int Order { get; set; }
    }
کلاس بالا یک متد به نام Invoke دارد که فراخوانی متد‌های مورد نظر برای تزریق کد را در دست خواهد گرفت. در این متد ابتدا عملیات لاگ در فایل مورد نظر ثبت می‌شود(با استفاده از Logger.WriteToFile). سپس با استفاده از getNext که از نوع GetNextHandlerDelegate است، اجرا را به کد‌های اصلی برنامه منتقل می‌کنیم.
 var methodReturn = getNext()( input , getNext );
برای مدیریت بهتر عملیات لاگ یک Attribute می‌سازیم که فقط متد هایی که نیاز به لاگ کردن دارند را مشخص کنیم. به صورت زیر:
 public class LogAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler( Microsoft.Practices.Unity.IUnityContainer container )
        {
            return new LogHandler();
        }
    }
فقط دقت داشته باشید که کلاس مورد نظر به جای ارث بری از کلاس Attribute باید از کلاس HandlerAttribute که در فضای نام Microsoft.Practices.Unity.InterceptionExtension  تعبیه شده است ارث ببرد(خود این کلاس از کلاس Attribute ارث برده است).  کافیست در متد CreateHandler آن که Override شده است یک نمونه از کلاس LogHandler را برگشت دهیم.
برای آماده سازی Ms Unity جهت عملیات Interception باید کد‌های زیر در ابتدا برنامه قرار داده شود:
var  unityContainer = new UnityContainer();

 unityContainer.AddNewExtension<Interception>();

  unityContainer.Configure<Interception>().SetDefaultInterceptorFor<IMyOperation>( new InterfaceInterceptor() );
            
  unityContainer.RegisterType<IMyOperation, MyOperation>();

توضیح چند مطلب:
بعد از نمونه سازی از کلاس UnityContainer باید Interception به عنوان یک Extension به این Container اضافه شود. سپس با استفاده از متد Configure برای اینترفیس IMyOperation یک Interceptor پیش فرض تعیین می‌کنیم. در پایان هم به وسیله متد RegisterType کلاس MyOperation  به اینترفیس IMyOperation رجیستر می‌شود. از این پس هر گاه درخواستی برای اینترفیس IMyOperation از unityContainer شود یک نمونه از کلاس MyOperation در اختیار خواهیم داشت.
به عنوان نکته آخر متد DoIt در اینترفیس بالا باید دارای LogAttribute باشد تا عملیات مزین سازی با کد‌های لاگ به درستی انجام شود.

یک نکته تکمیلی:
در هنگام مزین سازی متد  set خاصیت ها، به دلیل اینکه اینترفیسی برای این کار وجود ندارد باید مستقیما عملیات AOP به خود کلاس اعمال شود. برای این کار باید به صورت زیر عمل نمود:

var container = new UnityContainer();
container.RegisterType<Book , Book>();

container.AddNewExtension<Interception>();

 var policy = container.Configure<Interception>().SetDefaultInterceptorFor<Book>( new VirtualMethodInterceptor() ).AddPolicy( "MyPolicy" );

  policy.AddMatchingRule( new PropertyMatchingRule( "*" , PropertyMatchingOption.Set ) );
  policy.AddCallHandler<Handler.NotifyChangedHandler>();
همان طور که مشاهده می‌کنید عملیات Interception مستقیما برای کلاس پیکر بندی می‌شود و به جای InterfaceInterceptor از VirtualMethodInterceptor برای تزریق کد به بدنه متد‌ها استفاده شده است. در پایان نیز با تعریف یک Policy می‌توانیم به راحتی(با استفاده از "*") متد Set  تمام خواص کلاس را به NotifyChangedHandler مزین نماییم.

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

عموما بر روی سرورهای برنامه‌های وب، نرم افزار خاصی نصب نمی‌شود. برای مثال اگر نیاز به تولید فایل اکسل بر روی سرور باشد، سرور دار بعید است که آفیس را برای شما نصب کند و همچنین مایکروسافت هم این یک مورد را اصلا توصیه و پشتیبانی نمی‌کند (ایجاد چندین وهله از برنامه آفیس (تعامل با اشیاء COM) بر روی سرور توسط یک برنامه‌ی وب چند کاربره).
اگر سایت‌ها را هم جستجو کنید پر است از مقالاتی مانند تبدیل GridView به اکسل ... که تنها هنر آن‌ها انتخاب قسمت table مانند GridView و رندر کردن آن در مرورگر با پسوندی به نام xls یا xlsx است. به عبارتی فایل نهایی تولید شده استاندارد نیست. فقط یک html table است با پسوند xls/xlsx که برنامه‌ی اکسل می‌داند به چه صورتی باید آن‌را باز کند (که گاها در این بین فارسی سازی آن مشکل ساز می‌شود). این فایل نهایی تولیدی عاری است از امکانات پیشرفته‌ و حرفه‌ای اکسل. برای مثال اضافه کردن فرمول به آن، تبدیل اطلاعات به نمودارهای اکسل به صورت خودکار، داشتن فایلی با چندین work sheet‌ مختلف، اعمال قالب‌های مختلف، صفحه بندی بهتر و غیره.
مایکروسافت از سال 2007 تولید فایل‌های آفیس را با معرفی استاندارد OpenXML که توسط مؤسسه ایزو هم پذیرفته شده، بسیار ساده‌تر کرده است. OpenXML SDK‌ در دسترس است و توسط آن می‌توان فایل‌های اکسل حرفه‌ای را بدون نیاز به نصب مجموعه‌ی آفیس تولید کرد. کار کردن با OpenXML SDK هم در نگاه اول شاید ساده به نظر برسد اما آن هم ریزه کاری‌های خاص خودش را دارد که نمونه‌ای از آن‌را در مطلب "تولید فایل Word بدون نصب MS Word بر روی سرور" می‌توانید مشاهده کنید. به عبارتی این مجموعه جهت نوشتن کتابخانه‌های ویژه‌ی شما باز است ...
در این بین یکی از حرفه‌ای‌ترین کتابخانه‌هایی که امکانات تولید فایل‌های اکسل را به کمک OpenXML SDK‌ سهولت می‌بخشد، کتابخانه‌ی سورس باز EPPlus است:


مثالی در مورد نحوه‌ی استفاده از آن:
می‌خواهیم یک DataTable را به یک فایل اکسل واقعی (نه یک html table با پسوند xlsx) تبدیل کنیم با این شرایط که یکی از قالب‌های جدید آفیس به آن اعمال شود؛ جمع کل یکی از ستون‌ها توسط اکسل محاسبه گردیده و همچنین عرض دقیق ستون‌ها نیز در برنامه تنظیم گردد. نموداری نیز به صورت خودکار این اطلاعات را نمایش دهد:




using System.Data;
using System.IO;
using OfficeOpenXml;
using OfficeOpenXml.Drawing.Chart;
using OfficeOpenXml.Style;
using OfficeOpenXml.Table;

namespace EPPlusTest
{
class Program
{
static void Main(string[] args)
{
var newFile = new FileInfo("Test.xlsx");
if (newFile.Exists)
{
newFile.Delete();
}

//ایجاد یک سری اطلاعات دلخواه
var table = createDt();

using (var package = new ExcelPackage(newFile))
{
// اضافه کردن یک ورک شیت جدید
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("مخارج");

//اضافه کردن یک جدول جدید از دیتاتیبل دریافتی
worksheet.Cells["A1"].LoadFromDataTable(table, true, TableStyles.Dark9);

//نمایش جمع ستون هزینه‌های ماه‌ها
var tbl = worksheet.Tables[0];
//زیر آخرین ردیف یک سطر اضافه می‌کند
tbl.ShowTotal = true;
//فرمول نحوه‌ی محاسبه جمع ستون انتساب داده می‌شود
tbl.Columns[1].TotalsRowFunction = RowFunctions.Sum;

//تعیین عرض ستون‌های جدول
worksheet.Column(1).Width = 14;
worksheet.Column(2).Width = 12;

//تنظیم متن هدر
worksheet.HeaderFooter.oddHeader.CenteredText = "مثالی از نحوه‌ی استفاده از ایی پی پلاس";

//می‌خواهیم سرستون‌ها در وسط ستون قرار گیرند
worksheet.Cells["A1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
worksheet.Cells["B1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;

//افزودن یک نمودار جدید به شیت جاری
var chart = worksheet.Drawings.AddChart("chart1", eChartType.Pie3D);
chart.Title.Text = "نمودار هزینه‌های سال";
chart.SetPosition(Row: 2, RowOffsetPixels: 5, Column: 3, ColumnOffsetPixels: 5);
chart.SetSize(PixelWidth: 320, PixelHeight: 360);
chart.Series.Add("B2:B13", "A2:A13");
chart.Style = eChartStyle.Style26;

//تنظیم یک سری خواص فایل نهایی
package.Workbook.Properties.Title = "مثالی از ایی پی پلاس";
package.Workbook.Properties.Author = "وحید";
package.Workbook.Properties.Subject = "ایجاد فایل اکسل بدون نرم افزار اکسل";

//تنظیم نحوه‌ی نمایش فایل زمانیکه در نرم افزار اکسل گشوده می‌شود
worksheet.View.PageLayoutView = true;
worksheet.View.RightToLeft = true;

// ذخیر سازی کلیه موارد اعمالی در فایل
package.Save();
}
}

private static DataTable createDt()
{
var table = new DataTable("مخارج");
table.Columns.Add("ماه", typeof(string));
table.Columns.Add("هزینه", typeof(decimal));

table.Rows.Add("فروردین", 100);
table.Rows.Add("اردیبهشت", 250);
table.Rows.Add("خرداد", 80);
table.Rows.Add("تیر", 300);
table.Rows.Add("مرداد", 200);
table.Rows.Add("شهریور", 150);
table.Rows.Add("مهر", 250);
table.Rows.Add("آبان", 200);
table.Rows.Add("آذر", 400);
table.Rows.Add("دی", 100);
table.Rows.Add("بهمن", 130);
table.Rows.Add("اسفند", 80);
return table;
}
}
}

مطالب دوره‌ها
استفاده از AOP Interceptors برای حذف کدهای تکراری کش کردن اطلاعات در لایه سرویس برنامه
اکثر برنامه‌های ما دارای قابلیت‌هایی هستند که با موضوعاتی مانند امنیت، کش کردن اطلاعات، مدیریت استثناها، ثبت وقایع و غیره گره خورده‌اند. به هر یک از این موضوعات یک Aspect یا cross-cutting concern نیز گفته می‌شود.
در این قسمت قصد داریم اطلاعات بازگشتی از لایه سرویس برنامه را کش کنیم؛ اما نمی‌خواهیم مدام کدهای مرتبط با کش کردن اطلاعات را در مکان‌های مختلف لایه سرویس پراکنده کنیم. می‌خواهیم یک ویژگی یا Attribute سفارشی را تهیه کرده (مثلا به نام CacheMethod) و به متد یا متدهایی خاص اعمال کنیم. سپس برنامه، در زمان اجرا، بر اساس این ویژگی‌ها، خروجی‌های متدهای تزئین شده با ویژگی CacheMethod را کش کند.
در اینجا نیز از ترکیب StructureMap و DynamicProxy پروژه Castle، برای رسیدن به این مقصود استفاده خواهیم کرد. به کمک StructureMap می‌توان در زمان وهله سازی کلاس‌ها، آن‌ها را به کمک متدی به نام EnrichWith توسط یک محصور کننده دلخواه، مزین یا غنی سازی کرد. این مزین کننده را جهت دخالت در فراخوانی‌های متدها، یک DynamicProxy درنظر می‌گیریم. با پیاده سازی اینترفیس IInterceptor کتابخانه DynamicProxy مورد استفاده و تحت کنترل قرار دادن نحوه و زمان فراخوانی متدهای لایه سرویس، یکی از کارهایی را که می‌توان انجام داد، کش کردن نتایج است که در ادامه به جزئیات آن خواهیم پرداخت.


پیشنیازها

ابتدا یک برنامه جدید کنسول را آغاز کنید. تنظیمات آن‌را از حالت Client profile به Full تغییر دهید.
سپس همانند قسمت‌های قبل، ارجاعات لازم را به StructureMap و Castle.Core نیز اضافه نمائید:
 PM> Install-Package structuremap
PM> Install-Package Castle.Core
همچنین ارجاعی را به اسمبلی استاندارد System.Web.dll نیز اضافه نمائید.
از این جهت که از HttpRuntime.Cache قصد داریم استفاده کنیم. HttpRuntime.Cache در برنامه‌های کنسول نیز کار می‌کند. در این حالت از حافظه سیستم استفاده خواهد کرد و در پروژه‌های وب از کش IIS بهره می‌برد.


ویژگی CacheMethod مورد استفاده

using System;

namespace AOP02.Core
{
    [AttributeUsage(AttributeTargets.Method)]
    public class CacheMethodAttribute : Attribute
    {
        public CacheMethodAttribute()
        {
            // مقدار پیش فرض
            SecondsToCache = 10;
        }

        public double SecondsToCache { get; set; }
    }
}
همانطور که عنوان شد، قصد داریم متدهای مورد نظر را توسط یک ویژگی سفارشی، مزین سازیم تا تنها این موارد توسط AOP Interceptor مورد استفاده پردازش شوند.
در ویژگی CacheMethod، خاصیت SecondsToCache بیانگر مدت زمان کش شدن نتیجه متد خواهد بود.


ساختار لایه سرویس برنامه

using System;
using System.Threading;
using AOP02.Core;

namespace AOP02.Services
{
    public interface IMyService
    {
        string GetLongRunningResult(string input);
    }

    public class MyService : IMyService
    {
        [CacheMethod(SecondsToCache = 60)]
        public string GetLongRunningResult(string input)
        {
            Thread.Sleep(5000); // simulate a long running process
            return string.Format("Result of '{0}' returned at {1}", input, DateTime.Now);
        }
    }
}
اینترفیس IMyService و پیاده سازی نمونه آن‌را در اینجا مشاهده می‌کنید. از این لایه در برنامه استفاده شده و قصد داریم نتیجه بازگشت داده شده توسط متدی زمانبر را در اینجا توسط AOP Interceptors کش کنیم.


تدارک یک CacheInterceptor

using System;
using System.Web;
using Castle.DynamicProxy;

namespace AOP02.Core
{
    public class CacheInterceptor : IInterceptor
    {
        private static object lockObject = new object();

        public void Intercept(IInvocation invocation)
        {
            cacheMethod(invocation);
        }

        private static void cacheMethod(IInvocation invocation)
        {
            var cacheMethodAttribute = getCacheMethodAttribute(invocation);
            if (cacheMethodAttribute == null)
            {
                // متد جاری توسط ویژگی کش شدن مزین نشده است
                // بنابراین آن‌را اجرا کرده و کار را خاتمه می‌دهیم
                invocation.Proceed();
                return;
            }

            // دراینجا مدت زمان کش شدن متد از ویژگی کش دریافت می‌شود
            var cacheDuration = ((CacheMethodAttribute)cacheMethodAttribute).SecondsToCache;

            // برای ذخیره سازی اطلاعات در کش نیاز است یک کلید منحصربفرد را
            //  بر اساس نام متد و پارامترهای ارسالی به آن تهیه کنیم
            var cacheKey = getCacheKey(invocation);

            var cache = HttpRuntime.Cache;
            var cachedResult = cache.Get(cacheKey);


            if (cachedResult != null)
            {
                // اگر نتیجه بر اساس کلید تشکیل شده در کش موجود بود
                // همان را بازگشت می‌دهیم
                invocation.ReturnValue = cachedResult;
            }
            else
            {
                lock (lockObject)
                {
                    // در غیر اینصورت ابتدا متد را اجرا کرده
                    invocation.Proceed();
                    if (invocation.ReturnValue == null)
                        return;

                    // سپس نتیجه آن‌را کش می‌کنیم
                    cache.Insert(key: cacheKey,
                                 value: invocation.ReturnValue,
                                 dependencies: null,
                                 absoluteExpiration: DateTime.Now.AddSeconds(cacheDuration),
                                 slidingExpiration: TimeSpan.Zero);
                }
            }
        }

        private static Attribute getCacheMethodAttribute(IInvocation invocation)
        {
            var methodInfo = invocation.MethodInvocationTarget;
            if (methodInfo == null)
            {
                methodInfo = invocation.Method;
            }
            return Attribute.GetCustomAttribute(methodInfo, typeof(CacheMethodAttribute), true);
        }

        private static string getCacheKey(IInvocation invocation)
        {
            var cacheKey = invocation.Method.Name;

            foreach (var argument in invocation.Arguments)
            {
                cacheKey += ":" + argument;
            }

            // todo: بهتر است هش این کلید طولانی بازگشت داده شود
            // کار کردن با هش سریعتر خواهد بود
            return cacheKey;
        }
    }
}
کدهای CacheInterceptor مورد استفاده را در بالا مشاهده می‌کنید.
توضیحات ریز قسمت‌های مختلف آن به صورت کامنت، جهت درک بهتر عملیات، ذکر شده‌اند.


اتصال Interceptor به سیستم

خوب! تا اینجای کار صرفا تعاریف اولیه تدارک دیده شده‌اند. در ادامه نیاز است تا DI و DynamicProxy را از وجود آن‌ها مطلع کنیم.
using System;
using AOP02.Core;
using AOP02.Services;
using Castle.DynamicProxy;
using StructureMap;

namespace AOP02
{
    class Program
    {
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                var dynamicProxy = new ProxyGenerator();
                x.For<IMyService>()
                 .EnrichAllWith(myTypeInterface =>
                        dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new CacheInterceptor()))
                 .Use<MyService>();
            });

            var myService = ObjectFactory.GetInstance<IMyService>();
            Console.WriteLine(myService.GetLongRunningResult("Test"));
            Console.WriteLine(myService.GetLongRunningResult("Test"));
        }
    }
}
در قسمت تنظیمات اولیه DI مورد استفاده، هر زمان که شیءایی از نوع IMyService درخواست شود، کلاس MyService وهله سازی شده و سپس توسط CacheInterceptor محصور می‌گردد. اکنون ادامه برنامه با این شیء محصور شده کار می‌کند.
حال اگر برنامه را اجرا کنید یک چنین خروجی قابل مشاهده خواهد بود:
 Result of 'Test' returned at 2013/04/09 07:19:43
Result of 'Test' returned at 2013/04/09 07:19:43
همانطور که ملاحظه می‌کنید هر دو فراخوانی یک زمان را بازگشت داده‌اند که بیانگر کش شدن اطلاعات اولی و خوانده شدن اطلاعات فراخوانی دوم از کش می‌باشد (با توجه به یکی بودن پارامترهای هر دو فراخوانی).

از این پیاده سازی می‌شود به عنوان کش سطح دوم ORMها نیز استفاده کرد (صرفنظر از نوع ORM در حال استفاده).

دریافت مثال کامل این قسمت
AOP02.zip
نظرات اشتراک‌ها
نکاتی که باید جهت بررسی یکسان بودن دو URL بررسی کرد
موارد مهم این نکات رو اگر تبدیل به یک متد کمکی کنیم، کلاس زیر بدست خواهد آمد:
using System;
using System.Web;

namespace UrlNormalizationTest
{
    public static class UrlNormalization
    {
        public static bool AreTheSameUrls(this string url1, string url2)
        {
            url1 = url1.NormalizeUrl();
            url2 = url2.NormalizeUrl();
            return url1.Equals(url2);
        }

        public static bool AreTheSameUrls(this Uri uri1, Uri uri2)
        {
            var url1 = uri1.NormalizeUrl();
            var url2 = uri2.NormalizeUrl();
            return url1.Equals(url2);
        }

        public static string[] DefaultDirectoryIndexes = new[]
            {
                "default.asp",
                "default.aspx",
                "index.htm",
                "index.html",
                "index.php"
            };

        public static string NormalizeUrl(this Uri uri)
        {
            var url = urlToLower(uri);
            url = limitProtocols(url);
            url = removeDefaultDirectoryIndexes(url);
            url = removeTheFragment(url);
            url = removeDuplicateSlashes(url);
            url = addWww(url);
            url = removeFeedburnerPart(url);
            return removeTrailingSlashAndEmptyQuery(url);
        }

        public static string NormalizeUrl(this string url)
        {
            return NormalizeUrl(new Uri(url));
        }

        private static string removeFeedburnerPart(string url)
        {
            var idx = url.IndexOf("utm_source=", StringComparison.Ordinal);
            return idx == -1 ? url : url.Substring(0, idx - 1);
        }

        private static string addWww(string url)
        {
            if (new Uri(url).Host.Split('.').Length == 2 && !url.Contains("://www."))
            {
                return url.Replace("://", "://www.");
            }
            return url;
        }

        private static string removeDuplicateSlashes(string url)
        {
            var path = new Uri(url).AbsolutePath;
            return path.Contains("//") ? url.Replace(path, path.Replace("//", "/")) : url;
        }

        private static string limitProtocols(string url)
        {
            return new Uri(url).Scheme == "https" ? url.Replace("https://", "http://") : url;
        }

        private static string removeTheFragment(string url)
        {
            var fragment = new Uri(url).Fragment;
            return string.IsNullOrWhiteSpace(fragment) ? url : url.Replace(fragment, string.Empty);
        }

        private static string urlToLower(Uri uri)
        {
            return HttpUtility.UrlDecode(uri.AbsoluteUri.ToLowerInvariant());
        }

        private static string removeTrailingSlashAndEmptyQuery(string url)
        {
            return url
                    .TrimEnd(new[] { '?' })
                    .TrimEnd(new[] { '/' });
        }

        private static string removeDefaultDirectoryIndexes(string url)
        {
            foreach (var index in DefaultDirectoryIndexes)
            {
                if (url.EndsWith(index))
                {
                    url = url.TrimEnd(index.ToCharArray());
                    break;
                }
            }
            return url;
        }
    }
}
با این تست‌ها جهت بررسی آن:
using NUnit.Framework;
using UrlNormalizationTest;

namespace UrlNormalization.Tests
{
    [TestFixture]
    public class UnitTests
    {
        [Test]
        public void Test1ConvertingTheSchemeAndHostToLowercase()
        {
            var url1 = "HTTP://www.Example.com/".NormalizeUrl();
            var url2 = "http://www.example.com/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test2CapitalizingLettersInEscapeSequences()
        {
            var url1 = "http://www.example.com/a%c2%b1b".NormalizeUrl();
            var url2 = "http://www.example.com/a%C2%B1b".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test3DecodingPercentEncodedOctetsOfUnreservedCharacters()
        {
            var url1 = "http://www.example.com/%7Eusername/".NormalizeUrl();
            var url2 = "http://www.example.com/~username/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test4RemovingTheDefaultPort()
        {
            var url1 = "http://www.example.com:80/bar.html".NormalizeUrl();
            var url2 = "http://www.example.com/bar.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test5AddingTrailing()
        {
            var url1 = "http://www.example.com/alice".NormalizeUrl();
            var url2 = "http://www.example.com/alice/?".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test6RemovingDotSegments()
        {
            var url1 = "http://www.example.com/../a/b/../c/./d.html".NormalizeUrl();
            var url2 = "http://www.example.com/a/c/d.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test7RemovingDirectoryIndex1()
        {
            var url1 = "http://www.example.com/default.asp".NormalizeUrl();
            var url2 = "http://www.example.com/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test7RemovingDirectoryIndex2()
        {
            var url1 = "http://www.example.com/default.asp?id=1".NormalizeUrl();
            var url2 = "http://www.example.com/default.asp?id=1".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test7RemovingDirectoryIndex3()
        {
            var url1 = "http://www.example.com/a/index.html".NormalizeUrl();
            var url2 = "http://www.example.com/a/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test8RemovingTheFragment()
        {
            var url1 = "http://www.example.com/bar.html#section1".NormalizeUrl();
            var url2 = "http://www.example.com/bar.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test9LimitingProtocols()
        {
            var url1 = "https://www.example.com/".NormalizeUrl();
            var url2 = "http://www.example.com/".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test10RemovingDuplicateSlashes()
        {
            var url1 = "http://www.example.com/foo//bar.html".NormalizeUrl();
            var url2 = "http://www.example.com/foo/bar.html".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test11AddWww()
        {
            var url1 = "http://example.com/".NormalizeUrl();
            var url2 = "http://www.example.com".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }

        [Test]
        public void Test12RemoveFeedburnerPart()
        {
            var url1 = "http://site.net/2013/02/firefox-19-released/?utm_source=rss&utm_medium=rss&utm_campaign=firefox-19-released".NormalizeUrl();
            var url2 = "http://site.net/2013/02/firefox-19-released".NormalizeUrl();

            Assert.AreEqual(url1, url2);
        }
    }
}