نظرات مطالب
Blazor 5x - قسمت هشتم - مبانی Blazor - بخش 5 - تامین محتوای نمایشی کامپوننت‌های فرزند توسط کامپوننت والد
یک نکته‌ی تکمیلی: روش ارسال داده‌ها به RenderFragmentها

در مباحث اعتبارسنجی و احراز هویت Blazor در قسمت‌های بعدی، به قطعه کد context@ داری در داخل یک RenderFragment خواهیم رسید:
<AuthorizeView>
    <Authorized>
                Hello, @context.User.Identity.Name!
    </Authorized>
</AuthorizeView>
اکنون این سؤال مطرح است که این context@ از کجا آمده‌است و چه مفهومی را دارد؟
برای پاسخ به این سؤال نیاز است با مفهوم «Templated components» در برنامه‌های Blazor آشنا شد. تا اینجا از RenderFragmentها صرفا جهت فراهم آوردن قسمت ثابتی از قالب کامپوننت جاری، توسط استفاده کننده‌ی از آن، کمک گرفتیم. اما در همان سمت استفاده کننده، امکان دسترسی به اطلاعات مهیای داخل آن فرگمنت نیز وجود دارد. برای نمونه به کدهای کامپوننت TableTemplate.razor دقت کنید:
@typeparam Titem

<table class="table">
    <thead>
        <tr>@TableHeader</tr>
    </thead>
    <tbody>
        @foreach (var item in Items)
        {
            <tr>@RowTemplate(item)</tr>
        }
    </tbody>
</table>

@code {
    [Parameter]
    public RenderFragment TableHeader { get; set; }

    [Parameter]
    public RenderFragment<TItem> RowTemplate { get; set; }

    [Parameter]
    public IReadOnlyList<TItem> Items { get; set; }
}
- این کامپوننت، قالب سفارشی ثابت هدر جدول را توسط یک RenderFragment، از بکارگیرنده‌ی خود دریافت می‌کند.
- همچنین یک RenderFragment جنریک را نیز مشاهده می‌کنید که قالب ردیف‌های جدول را تامین می‌کند. نوع جنریک قابل دسترسی در این کامپوننت، توسط دایرکتیو typeparam Titem@ تعریف شده‌ی در ابتدای آن، مشخص شده‌است.
- بنابراین هربار که ردیفی از بدنه‌ی جدول در حال رندر است، یک شیء item از نوع TItem را به قالب سفارشی تامین شده‌ی توسط بکارگیرنده‌ی خود ارسال می‌کند.
اکنون این سؤال مطرح می‌شود که چگونه می‌توان به شیء item، در سمت والد یا بکارگیرنده‌ی کامپوننت TableTemplate فوق دسترسی یافت؟
برای اینکار می‌توان از پارامتر ویژه‌ای به نام context که یک implicit parameter است و به صورت پیش‌فرض تعریف شده و مهیا است، در سمت کدهای بکارگیرند‌ه‌ی کامپوننت، استفاده کرد:
@page "/pets"
<h1>Pets</h1>
<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
        new Pet { PetId = 4, Name = "Salem Saberhagen" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string Name { get; set; }
    }
}
- در اینجا روش بکارگیری کامپوننت TableTemplate را در کامپوننتی دیگر مشاهده می‌کنید. توسط فرگمنت TableHeader، قالب ثابت هدر این جدول تامین شده‌است. توسط فرگمنت RowTemplate قالب پویای ردیف‌های جدول ارائه شده‌است.
- همچنین در اینجا پارامتر ویژه‌ای به نام context@ را نیز مشاهده می‌کنید. این پارامتر همان شیء item ای است که در حین رندر هر ردیف جدول، به فرگمنت RowTemplate ارسال می‌شود. به این ترتیب کامپوننت والد می‌تواند از اطلاعات در حال رندر توسط کامپوننت فرگمنت دار، مطلع شود و از آن استفاده کند. در این مثال، نوع context@، از نوع کلاس Pet است که سعی شده بر اساس نوع پارامتر Items ارسالی به آن، توسط کامپایلر تشخیص داده شود. حتی می‌توان این نوع را به صورت صریحی نیز مشخص کرد:
<TableTemplate Items="pets" TItem="Pet">
این مورد زمانی مفید است که چندین پارامتر جنریک را در کامپوننت تعریف کرده باشیم:
<SomeGenericComponent TParam1="Person" TParam2="Supplier" TItem=etc/>
و یا می‌توان نام پارامتر ویژه‌ی context@ را در تمام RenderFragmentهای جنریک، با ذکر ویژگی Context برای مثال به pet تغییر داد:
<TableTemplate Items="pets" Context="pet">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>
و یا حتی می‌توان این نام را به صورت اختصاصی به ازای هر RenderFragment جنریک، مشخص کرد. برای اینکار ویژگی Context را دقیقا بر روی RenderFragment مدنظر قرار می‌دهیم:
<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate Context="pet">
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>
این تغییرنام بهتر است زمانی صورت گیرد که نام پیش‌فرض context، با نام مشابه دیگری در کامپوننت جاری، تداخل پیدا کرده‌است.
نظرات مطالب
مقایسه بین حلقه های تکرار (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) بصورت زیر:

البته نتایج این تستها مطلق نیستن. نکاتی که در کامنت قبلی اشاره کردم از عوامل تاثیرگذار هستن.
موفق باشین.
مطالب
روش دیگر نوشتن Model binderهای سفارشی در ASP.NET Core 7x با معرفی IParseable
ASP.NET MVC از روش بکارگیری binding providerها برای تدارک زیرساخت model binding استفاده می‌کند که در این روش، داده‌های پارامترهای یک action method از طریق هدرها، کوئری استرینگ‌ها، بدنه‌ی درخواست و غیره تهیه می‌شوند. در حالت پیش‌فرض اگر این پارامترها از نوع‌های ساده‌ای مانند اعداد و یا DateTime تشکیل شده باشند و یا به همراه یک TypeConverter باشند که امکان تبدیل این رشته را به آن نوع خاص بدهد، به صورت خودکار bind خواهند شد و هر نوع دیگری، به صورت یک نوع پیچیده درنظر گرفته می‌شود. نوع پیچیده یعنی bind برای مثال اطلاعات بدنه‌ی درخواست به تک تک خواص آن نوع. برای نمونه در کنترلر زیر:
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries =
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy",
        "Hot", "Sweltering", "Scorching",
    };

    // /WeatherForecast/GetForecast2?from=1&to=3
    [HttpGet("[action]")]
    public IEnumerable<WeatherForecast> GetForecast2(Duration days)
    {
        return Enumerable.Range(days.From, days.To)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = Summaries[Random.Shared.Next(Summaries.Length)],
                                          })
                         .ToArray();
    }
}
که از دو مدل زیر استفاده می‌کند:
public class Duration
{
    public int From { get; set; }
    public int To { get; set; }
}

public class WeatherForecast
{
    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }
}
می‌توان خواص پارامتر days را از طریق کوئری استرینگ‌های HttpGet، برای مثال با ارائه‌ی آدرس WeatherForecast/GetForecast2?from=1&to=3 به صورت خودکار تامین کرد. زمانیکه اطلاعات رسیده چنین شکلی را داشته باشند، کار پردازش و bind آن‌ها در حالت HttpGet، خودکار است.


روش دیگر پردازش اطلاعات رشته‌ای رسیده و تشکیل یک Model Binder سفارشی در ASP.NET Core 7x

اکنون فرض کنید بجای آدرس WeatherForecast/GetForecast2?from=1&to=3 که اطلاعات را از طریق کوئری استرینگ مشخص و استانداردی دریافت می‌کند، می‌خواهیم اطلاعات آن‌را از طریق یک قالب سفارشی و غیراستاندارد مانند WeatherForecast/GetForecast3/1-3 دریافت کنیم:
// /WeatherForecast/GetForecast3/1-3
[HttpGet("[action]/{days}")]
public IEnumerable<WeatherForecast> GetForecast3(Days days)
    {
        return Enumerable.Range(days.From, days.To)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = Summaries[Random.Shared.Next(Summaries.Length)],
                                          })
                         .ToArray();
    }
یکی از راه‌های انجام اینکار، نوشتن model binderهای سفارشی مخصوص است و یا اکنون در ASP.NET Core 7x می‌توان با پیاده سازی اینترفیس IParsable به صورت خودکار و با روشی دیگر به این مقصود رسید:
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace NET7Mvc.Models;

public class Days : IParsable<Days>
{
    public Days(int from, int to)
    {
        From = from;
        To = to;
    }

    public int From { get; }
    public int To { get; }

    public static bool TryParse([NotNullWhen(true)] string? value,
                                IFormatProvider? provider,
                                [MaybeNullWhen(false)] out Days result)
    {
        var items = value?.Split('-', StringSplitOptions.RemoveEmptyEntries);
        if (items is { Length: 2 })
        {
            if (int.TryParse(items[0], NumberStyles.None, provider, out var from)
                && int.TryParse(items[1], NumberStyles.None, provider, out var to))
            {
                result = new Days(from, to);
                return true;
            }
        }

        result = default;
        return false;
    }

    public static Days Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
            throw new ArgumentException("Could not parse the given value.", nameof(value));
        }

        return result;
    }
}
- برای پیاده سازی این اینترفیس باید دو متد TryParse و Parse آن‌را به صورت فوق پیاده سازی کرد و توسط آن، روش تبدیل رشته‌ی دریافتی از کاربر را به شیء مدنظر، مشخص کرد.
- همینقدر که مدلی IParsable را پیاده سازی کرده باشد، از امکانات آن به صورت خودکار استفاده خواهد شد و نیازی به معرفی و یا تنظیمات خاص دیگری ندارد.
- البته این قابلیت جدید نیست و پشتیبانی از IParsable، پیشتر در Minimal API دات نت 6 ارائه شده بود؛ اما در دات نت 7 توسط ASP.NET Core MVC نیز قابل استفاده شده‌است.
مطالب
ساخت یک سایت ساده‌‌ی نمایش لیست فیلم با استفاده از Vue.js - قسمت اول
Vue.js  یکی از محبوب ترین فریم ورک‌های  SPA است و سایت جاری نیز دارای مقالات خوبی درباره‌ی Vue.js می‌باشد. قصد داریم طی چند مقاله با استفاده از Vue.js و چندین پلاگین مطرح آن، یک سایت ساده‌ی نمایش فیلم را ایجاد کنیم. ابتدا Node.js  را بر روی سیستم خود نصب کنید (پیشنهاد ما نسخه‌ی LTS می‌باشد). مراحل نصب آن ساده است و بصورت Nextهایی پی در پی می‌باشد؛ بصورت پیش فرض npm نیز همراه آن نصب میشود. سپس دو دستور زیر را جهت صحت انجام مراحل نصب، تست نمایید.

در این مقاله با ادیتور VS Code کار میکنیم. بعد از نصب آن، از منوی Terminal، گزینه‌ی New Terminal را کلیک کنید تا پنجره‌ی PowerShell نمایش داده شود؛ برای سرعت و دقت بیشتر در برنامه‌های  vue.js ای. با دستور زیر vue cli را  نصب میکنیم  (فقط یک مرتبه و برای برنامه‌های بعدی vue.jsای، نیازی به اجرای این دستور نداریم):

npm install -g @vue/cli

جهت راه اندازی یک برنامه‌ی پیش فرض Vue.js ای، کافیست دستور زیر را اجرا نماییم تا پکیج‌های مورد نیاز، به همراه کانفیگ اولیه (Zero config) برای ما ایجاد شوند:

vue create movie-app

بعد از ایجاد برنامه در vs code، از طریق منوی File، گزینه Open Folder را کلیک کرده و پوشه برنامه‌ای را که ایجاد کردیم، Select Folder میکنیم. ساختار اولیه‌ی برنامه‌ی ایجاد شده، به شکل زیر می‌باشد:

نیازمندیهای مثال جاری

A) برای گرفتن اطلاعات مورد نمایش در مثال جاری، از سایت omdbapi.com استفاده میکنیم که با دریافت یک api key آن بصورت رایگان، میتوانیم web serviceهای آن را Call نماییم.

B) از  vuetify برای ui استفاده میکنیم که بصورت Material Design و دارای کامپوننت‌های غنی می‌باشد؛ ضمن اینکه RTL را هم پشتیبانی میکند.

برای نصب آن در Terminal دستور زیر را اجرا میکنیم:

vue add vuetify

سپس جهت تست و صحت افزوده شدن و کانفیگ درست، با دستور زیر برنامه را اجرا میکنیم:

npm run serve

بعد از اجرای دستور فوق، روی گزینه زیر ctrl+click میکنیم تا نتیجه کار در مرورگر قابل رویت باشد:

نمایش صفحه زیر نشان دهنده‌ی درستی انجام کار تا اینجا است:


نکته: جهت استفاده از امکان RTL کافیست در فایل vuetify.js واقع در پوشه‌ی plugins، تغییرات زیر را انجام دهیم. در مثال جاری بدلیل اینکه اطلاعات انگلیسی می‌باشند، از نسخه LTR آن استفاده میکنیم؛ هر چند یکسری api فارسی نیز موجود می‌باشد که میتوان از آنها استفاده نمود.

import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import 'vuetify/src/stylus/app.styl'

Vue.use(Vuetify, {
  iconfont: 'md',
  rtl: true
})


C) نصب  vue-router : جهت انجام routeهای تودرتو ، مپ کردن کامپوننت ها با آدرسی مشخص، کار با پارامتر و  HTML5 History API  مورد استفاده قرار میگیرد. برای نصب آن، دستور زیر را اجرا میکنیم:

npm install vue-router

برای نوشتن routeهای مورد نیاز، یک فولدر را با نام router، در پوشه src برنامه ایجاد میکنیم و یک فایل جاوا اسکریپتی را در آن با نام index.js، میسازیم (این ساختار برای مدیریت بهتر پروژه می‌باشد):

درون فایل  index.js، محتویات زیر را طبق مستندات آن قرار میدهیم:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

جهت استفاده از این router، نیاز است تا در نمونه‌ی وهله سازی شده‌ی vue برنامه بکار گرفته شود. فایل  main.js  را باز کنید و خط زیر را در قسمت بالای برنامه وارد کنید:

import router from './router'

اکنون محتویات فایل  main.js بشکل زیر می‌باشد:

import Vue from 'vue'
import './plugins/vuetify'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')


D) نصب axios : برای انجام  درخواستهای  HTTP  و عملیات ا‌ی‌جکس در vue.js  ترجیحا بهتر است از axios که یک کتابخانه‌ی محبوب می‌باشد و کار با آن ساده است، استفاده شود. برای نصب آن، دستور زیر را اجرا میکنیم:

npm install axios


E) نصب vuex : کتابخانه‌ای جهت مدیریت حالت (state management) برای  vue.js میباشد و مشابه آن Flux و Redux برای React می‌باشند. برای  نصب، دستور زیر را اجرا میکنیم:

npm install vuex


برای بکارگیری آن یک فولدر را با نام store در پوشه‌ی src برنامه ایجاد میکنیم و یک فایل جاوا اسکریپتی را در آن با نام index.js میسازیم (این ساختار برای مدیریت بهتر پروژه می‌باشد). درون فایل  index.js، محتویات زیر را طبق مستندات آن و ^ قرار میدهیم. 

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store()

برای استفاده و کانفیگ آن، محتویات فایل  main.js را بشکل زیر تغییر دهید:

import Vue from 'vue'
import './plugins/vuetify'
import App from './App.vue'
import router from './router'
import {store} from './store'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store,
  router
}).$mount('#app')



دریافت کد قسمت اول 

نکته: برای اجرای برنامه و دریافت پکیج‌های مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:

npm install
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت هفتم - سفارشی سازی ظاهر مستندات API
امکان کنترل کامل و سفارشی سازی ظاهر نهایی Swagger-UI وجود دارد که جزئیات آن‌را در این قسمت بررسی خواهیم کرد.


بهبود ظاهر کامنت‌ها با بکارگیری Markdown

Markdown زبان سبکی است برای تعیین شیوه‌نامه‌ی نمایش متون ساده. اگر پیشتر با سیستم ارسال نظرات Github و یا Stackoverflow کار کرده باشید، قطعا با آن آشنایی دارید. توضیحات کامل و جزئیات آن‌را می‌توانید در آدرس markdownguide.org مطالعه کنید. خوشبختانه امکان استفاده‌ی از Markdown در OpenAPI spec نیز وجود دارد که سبب بهبود ظاهر مستندات نهایی حاصل از آن خواهد شد.
در قسمت سوم، سعی کردیم مثالی را توسط remarks، به قسمت Patch اضافه کنیم که ظاهر آن، آنچنان مطلوب به نظر نمی‌رسد و بهتر است آن‌را به صورت یک قطعه کد نمایش داد:


برای بهبود این ظاهر می‌توان از Markdown استفاده کرد. بنابراین ابتدا تمام backslash‌های اضافه شده را که جهت نمایش خطوط جدید اضافه شده بودند، حذف می‌کنیم. در Markdown خطوط جدید با درج حداقل 2 فاصله (space) و یک سطر جدید مشخص می‌شوند. همچنین استفاده‌ی از ** سبب ضخیم شدن نمایش عبارت‌ها می‌شود. برای اینکه قطعه کد نوشته شده را در Markdown به صورت کدی با پس زمینه‌ای مشخص نمایش دهیم، پیش از شروع هر سطر آن نیاز است یک tab و یا 4 فاصله (space) درج شوند:
/// <remarks>
/// Sample request (this request updates the author's **first name**)
///
///     PATCH /authors/id
///     [
///         {
///           "op": "replace",
///           "path": "/firstname",
///           "value": "new first name"
///           }
///     ]
/// </remarks>
پس از این تغییرات خواهیم داشت:

که نسبت به حالت قبلی بسیار بهتر به نظر می‌رسد.
در نگارش فعلی، استفاده‌ی از Markdown برای توضیحات remarks، پارامترها و response codes پشتیبانی می‌شود؛ اما نه برای قسمت summary که برای نگارش بعدی درنظر گرفته شده‌است. همچنین از قابلیت‌های پیشترفته‌ی Markdown هم استفاده نکنید (در کل نیاز به مقداری سعی و خطا دارد تا مشخص شود چه قابلیت‌هایی را پشتیبانی می‌کند).


سفارشی سازی مقدماتی UI به کمک تنظیمات API آن

جائیکه تنظیمات میان‌افزار Swashbuckle.AspNetCore در کلاس Starup تعریف می‌شوند، می‌توان تغییراتی را نیز در UI آن اعمال کرد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
    // ...
            app.UseSwaggerUI(setupAction =>
            {
                setupAction.SwaggerEndpoint(
                    url: "/swagger/LibraryOpenAPISpecification/swagger.json",
                    name: "Library API");
                setupAction.RoutePrefix = "";

                setupAction.DefaultModelExpandDepth(2);
                setupAction.DefaultModelRendering(Swashbuckle.AspNetCore.SwaggerUI.ModelRendering.Model);
                setupAction.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
                setupAction.EnableDeepLinking();
                setupAction.DisplayOperationId();
            });
    // ...
        }
    }
}
با این خروجی که به علت تنظیم DocExpansion آن به None، اینبار لیست قابلیت‌ها را به صورت باز شده نمایش نمی‌دهد:


همچنین چون DefaultModelRendering به Model تنظیم شده‌است، اینبار بجای مثال، مشخصات مدل را به صورت پیش‌فرض نمایش می‌دهد:


کار DisplayOperationId نمایش Id هر Operation است؛ مانند get_api_authors. در اینجا Operation همان مداخل API ما هستند و به عنوان هر قسمت، Tag گفته می‌شود؛ مانند Authors و یا Books:


با فعالسازی EnableDeepLinking، آدرس‌هایی مانند tagName/# و یا tagName/OperationId/# قابلیت مرور و بازشدن خودکار را پیدا می‌کنند (tagName همان نام کنترلر است و OperationId همان اکشن متدی که عمومی شده‌است). برای مثال اگر آدرس https://localhost:5001/index.html#/Books/get_api_authors__authorId__books را در یک برگه‌ی جدید مرورگر باز کنیم، بلافاصله گروه Books، باز شده و سپس به اکشن متد یا مدخلی که Id آن ذکر شده‌است، هدایت می‌شویم:



اعمال تغییرات پیشرفته در UI

Swagger-UI در اصل از یک سری فایل html، css، js و فونت تشکیل شده‌است که آن‌ها را می‌توانید در آدرس src/Swashbuckle.AspNetCore.SwaggerUI مشاهده کنید. برای مثال فایل index.html آن‌را در اینجا می‌توانید مشاهده کنید. اصل آن در div ای با id مساوی swagger-ui رخ می‌دهد و در این قسمت است که رابط کاربری آن به صورت پویا تولید شده و رندر خواهد شد. بررسی فایل‌های js و css آن در این مخزن کد مشکل است؛ چون نگارش فشرده شده‌ی آن هستند. به همین جهت می‌توان به مخزن کد اصلی Swagger-UI که نگارش جایگذاری شده‌ی آن (embedded) توسط Swashbuckle.AspNetCore ارائه می‌شود، رجوع کرد. برای مثال در پوشه‌ی src/styles آن، اصل فایل‌های css آن برای سفارشی سازی وجود دارند.

پس از اعمال تغییرات خود، می‌توانید css و یا js سفارشی خود را به نحو زیر به تنظیمات app.UseSwaggerUI سیستم معرفی کنید:
setupAction.InjectStylesheet("/Assets/custom-ui.css");
setupAction.InjectJavaScript("/Assets/custom-js.js");
باید دقت داشت که این فایل‌ها باید در پوشه‌ی wwwroot قرار گرفته و قابل دسترسی باشند.
برای اعمال تغییرات اساسی و تزریق صفحه‌ی index.html جدیدی، می‌توان به صورت زیر عمل کرد:
setupAction.IndexStream = () => 
    GetType().Assembly.GetManifestResourceStream(
  "OpenAPISwaggerDoc.Web.EmbeddedAssets.index.html");
نکته‌ی مهم: اینبار این فایل باید به صورت embedded ارائه شود. به همین جهت در مثال فوق، عبارت OpenAPISwaggerDoc.Web به فضای نام اصلی اسمبلی جاری اشاره می‌کند. سپس EmbeddedAssets، نام پوشه‌ای است که فایل index.html سفارشی سازی شده، در آن قرار خواهد گرفت. اکنون برای اینکه این فایل را به صورت EmbeddedResource معرفی کنیم، نیاز است فایل csproj را به نحو زیر ویرایش کرد:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <None Remove="EmbeddedAssets\index.html" />
  </ItemGroup>

  <ItemGroup>
    <EmbeddedResource Include="EmbeddedAssets\index.html" />
  </ItemGroup>
</Project>

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-07.zip
مطالب
افزونه farsiInput جهت ورودی فقط فارسی در صفحات وب
گاهی از اوقات نیاز است کاربر در یک جعبه متنی، فقط متن فارسی وارد کند؛ حتی اگر صفحه کلید او فارسی نباشد و یا بنابر درخواست او، جهت بالا رفتن سرعت ورود اطلاعات یک چنین قابلیتی نیاز می‌شود. چندین سال قبل farsitype.js اینکار را انجام می‌داد. این اسکریپت با مرورگرهای جدید سازگار نیست و برای نمونه initKeyEvent آن در نگارش‌های قدیمی فایرفاکس کار می‌کرد، در کروم هیچ وقت پشتیبانی نشد (به نام initKeyboardEvent موجوداست؛ اما برای جایگزین کردن حروف عمل نمی‌کند) و مدتی است که فایرفاکس هم به دلایل امنیتی آن‌را غیرفعال کرده است.
به همین جهت افزونه farsiInput، که کدهای آن‌را در ادامه مشاهده می‌کنید، تهیه گردید. این افزونه تا این تاریخ با IE، فایرفاکس، کروم و اپرا سازگار است و توسط آن کاربر بدون نیاز به داشتن یک صفحه کلید فارسی می‌تواند فارسی تایپ کند. برای سوئیچ به حالت انگلیسی، دکمه Scroll lock باید روشن شود و این مورد توسط پارامتر changeLanguageKey قابل تغییر است.
// <![CDATA[
(function ($) {
    $.fn.farsiInput = function (options) {
        var defaults = {
            changeLanguageKey: 145 /* Scroll lock */
        };
        var options = $.extend(defaults, options);

        var lang = 'fa';

        var keys = new Array(1711, 0, 0, 0, 0, 1608, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1705, 1572, 0, 1548,
                             1567, 0, 1616, 1571, 8250, 0, 1615, 0, 0, 1570, 1577, 0, 0, 0, 1569, 1573, 0, 0, 1614, 1612, 1613, 0, 0,
                             8249, 1611, 171, 0, 187, 1580, 1688, 1670, 0, 1600, 1662, 1588, 1584, 1586, 1740, 1579, 1576, 1604, 1575,
                             1607, 1578, 1606, 1605, 1574, 1583, 1582, 1581, 1590, 1602, 1587, 1601, 1593, 1585, 1589, 1591, 1594, 1592);

        var substituteChar = function (charCode, e) {
            if (navigator.appName == "Microsoft Internet Explorer") {
                window.event.keyCode = charCode;
            }
            else {
                insertAtCaret(String.fromCharCode(charCode), e);
            }
        };

        var insertAtCaret = function (str, e) {
            var obj = e.target;
            var startPos = obj.selectionStart;
            var endPos = obj.selectionEnd;
            var scrollTop = obj.scrollTop;
            obj.value = obj.value.substring(0, startPos) + str + obj.value.substring(endPos, obj.value.length);
            obj.focus();
            obj.selectionStart = startPos + str.length;
            obj.selectionEnd = startPos + str.length;
            obj.scrollTop = scrollTop;
            e.preventDefault();
        };

        var keyDown = function (e) {
            var evt = e || window.event;
            var key = evt.keyCode ? evt.keyCode : evt.which;
            if (key == options.changeLanguageKey) {
                lang = (lang == 'en') ? 'fa' : 'en';
                return true;
            }
        };

        var fixYeKeHalfSpace = function (key, evt) {
            var originalKey = key;
            var arabicYeCharCode = 1610;
            var persianYeCharCode = 1740;
            var arabicKeCharCode = 1603;
            var persianKeCharCode = 1705;
            var halfSpace = 8204;

            switch (key) {
                case arabicYeCharCode:
                    key = persianYeCharCode;
                    break;
                case arabicKeCharCode:
                    key = persianKeCharCode;
                    break;
            }

            if (evt.shiftKey && key == 32) {
                key = halfSpace;
            }

            if (originalKey != key) {
                substituteChar(key, evt);
            }
        };

        var keyPress = function (e) {
            if (lang != 'fa')
                return;

            var evt = e || window.event;
            var key = evt.keyCode ? evt.keyCode : evt.which;
            fixYeKeHalfSpace(key, evt);
            var isNotArrowKey = (evt.charCode != 0) && (evt.which != 0);
            if (isNotArrowKey && (key > 38) && (key < 123)) {
                var pCode = (keys[key - 39]) ? (keys[key - 39]) : key;
                substituteChar(pCode, evt);
            }
        }

        return this.each(function () {
            var input = $(this);
            input.keypress(function (e) {
                keyPress(e);
            });
            input.keydown(function (e) {
                keyDown(e);
            });
        });
    };
})(jQuery);
// ]]>
مثالی از نحوه بکارگیری آن:
<html>
<head>
    <title>تکست باکس فارسی</title>
    <script type="text/javascript" src="jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="jquery.farsiInput.js"></script>
    <style type="text/css">
        input, textarea
        {
            font-family: tahoma;
            font-size: 9pt;
        }
    </style>
</head>
<body>
    <input dir="rtl" id='text1' />
    <br />
    <textarea dir="rtl" id='text2' rows="15" cols="84"></textarea>
    <script type="text/javascript">
        $(function () {
            $("#text1, #text2").farsiInput();
        });
    </script>
</body>
</html>

دریافت کدهای کامل افزونه farsiInput
farsi_input.zip
 
مطالب
نحوه‌ی نگاشت فیلدهای فرمول در Fluent NHibernate

اگر با SQL Server کار کرده باشید حتما با مفهوم و امکان Computed columns (فیلدهای محاسبه شده) آن آشنایی دارید. چقدر خوب می‌شد اگر این امکان برای سایر بانک‌های اطلاعاتی که از تعریف فیلدهای محاسبه شده پشتیبانی نمی‌کنند، نیز مهیا می‌شد. زیرا یکی از اهداف مهم استفاده‌ی صحیح از ORMs ، مستقل شدن برنامه از نوع بانک اطلاعاتی است. برای مثال امروز می‌خواهیم با MySQL‌ کار کنیم، ماه بعد شاید بخواهیم یک نسخه‌ی سبک‌تر مخصوص کار با SQLite را ارائه دهیم. آیا باید قسمت دسترسی به داده برنامه را از نو بازنویسی کرد؟ اینکار در NHibernate فقط با تغییر نحوه‌ی اتصال به بانک اطلاعاتی میسر است و نه بازنویسی کل برنامه (و صد البته شرط مهم و اصلی آن هم این است که از امکانات ذاتی خود NHibernate استفاده کرده باشید. برای مثال وسوسه‌ی استفاده از رویه‌های ذخیره شده را فراموش کرده و به عبارتی ORM مورد استفاده را به امکانات ویژه‌ی یک بانک اطلاعاتی گره نزده باشید).
خوشبختانه در NHibernate امکان تعریف فیلدهای محاسباتی با کمک تعریف نگاشت خواص به صورت فرمول مهیا است. برای توضیحات بیشتر لطفا به مثال ذیل دقت بفرمائید:
در ابتدا کلاس کاربر تعریف می‌شود:

using System;
using NHibernate.Validator.Constraints;

namespace FormulaTests.Domain
{
public class User
{
public virtual int Id { get; set; }

[NotNull]
public virtual DateTime JoinDate { set; get; }

[NotNullNotEmpty]
[Length(450)]
public virtual string FirstName { get; set; }

[NotNullNotEmpty]
[Length(450)]
public virtual string LastName { get; set; }

[Length(900)]
public virtual string FullName { get; private set; } //از طریق تعریف فرمول مقدار دهی می‌گردد

public virtual int DayOfWeek { get; private set; }//از طریق تعریف فرمول مقدار دهی می‌گردد
}
}
در این کلاس دو خاصیت FullName و DayOfWeek به صورت فقط خواندنی به کمک private set ذکر شده، تعریف گردیده‌اند. قصد داریم روی این دو خاصیت فرمول تعریف کنیم:

using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace FormulaTests.Domain
{
public class UserCustomMappings : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(u => u.Id).GeneratedBy.Identity(); //ضروری است
mapping.Map(x => x.DayOfWeek).Formula("DATEPART(dw, JoinDate) - 1");
mapping.Map(x => x.FullName).Formula("FirstName + ' ' + LastName");
}
}
}
نحوه‌ی انتساب فرمول‌های مبتنی بر SQL را در نگاشت فوق ملاحظه می‌نمائید. برای مثال FullName از جمع دو فیلد نام و نام خانوادگی حاصل خواهد شد و DayOfWeek از طریق فرمول SQL دیگری که ملاحظه می‌نمائید (یا هر فرمول SQL دلخواه دیگری که صلاح می‌دانید).
اکنون اگر Fluent NHibernate را وادار به تولید اسکریپت متناظر با این دو کلاس کنیم حاصل به صورت زیر خواهد بود:
    create table Users (
UserId INT IDENTITY NOT NULL,
JoinDate DATETIME not null,
FirstName NVARCHAR(450) not null,
LastName NVARCHAR(450) not null,
primary key (UserId)
)
همانطور که ملاحظه می‌کنید در اینجا خبری از دو فیلد محاسباتی تعریف شده نیست. این فیلدها در تعاریف نگاشت‌ها به صورت خودکار ظاهر می‌شوند:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true"
name="FormulaTests.Domain.User, FormulaTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Users">
<id name="Id" type="System.Int32" unsaved-value="0">
<column name="UserId" />
<generator class="identity" />
</id>
<property name="DayOfWeek" formula="DATEPART(dw, JoinDate) - 1" type="System.Int32" />
<property name="FullName" formula="FirstName + ' ' + LastName" type="System.String" />
<property name="JoinDate" type="System.DateTime">
<column name="JoinDate" />
</property>
<property name="FirstName" type="System.String">
<column name="FirstName" />
</property>
<property name="LastName" type="System.String">
<column name="LastName" />
</property>
</class>
</hibernate-mapping>
اکنون اگر کوئری زیر را در برنامه اجرا نمائیم:
var list = session.Query<User>.ToList();
foreach (var item in list)
{
Console.WriteLine("{0}:{1}", item.FullName, item.DayOfWeek);
}
به صورت خودکار به SQL ذیل ترجمه خواهد شد و اکنون نحوه‌ی بکارگیری فیلدهای فرمول، بهتر مشخص می‌گردد:
select
user0_.UserId as UserId0_,
user0_.JoinDate as JoinDate0_,
user0_.FirstName as FirstName0_,
user0_.LastName as LastName0_,
DATEPART(user0_.dw, user0_.JoinDate) - 1 as formula0_, --- همان فرمول تعریف شده است
user0_.FirstName + ' ' + user0_.LastName as formula1_ ---از طریق فرمول تعریف شده حاصل گردیده است
from
Users user0_

مطالب
راهبری در Silverlight به کمک الگوی MVVM

مقدمات راهبری (Navigation) در سیلورلایت را در اینجا می‌توانید مطالعه نمائید : +
مطلبی را که در فصل فوق نخواهید یافت در مورد نحوه‌ی بکارگیری الگوی MVVM جهت پیاده سازی Navigation در یک برنامه‌ی سیلورلایت است؛ علت آن هم به این بر می‌گردد که این فصل پیش از مباحث Binding مطرح شد.

صورت مساله:
یکی از اصول MVVM این است که در ViewModel‌ نباید ارجاعی از View وجود داشته باشد (ViewModel باید در بی‌خبری کامل از وجود اشیاء UI و ارجاع مستقیم به آن‌ها طراحی شود)، اما برای پیاده سازی مباحث Navigation نیاز است به نحوی به شیء Frame قرار داده شده در صفحه‌ی اصلی یا قالب اصلی برنامه دسترسی یافت تا بتوان درخواست رهنمون شدن به صفحات مختلف را صادر کرد. اکنون چکار باید کرد؟

راه حل:
یکی از راه حل‌های جالبی که برای این منظور وجود دارد استفاده از امکانات کلاس Messenger مجموعه‌ی MVVM Light toolkit است. از طریق ViewModel برنامه، آدرس صفحه‌ی مورد نظر را به صورت یک پیغام به View مورد نظر ارسال می‌کنیم و سپس View برنامه که به این پیغام‌ها گوش فرا می‌دهد، پس از دریافت آدرس مورد نظر، نسبت به فراخوانی تابع Navigate شیء Frame رابط کاربری برنامه اقدام خواهد کرد. به این صورت ViewModel برنامه به View خود جهت اعمال راهبری برنامه، گره نخواهد خورد.

روش پیاده سازی:
ابتدا ساختار پروژه را در نظر بگیرید (این شکل دگرگون شده‌ی Solution explorer مرتبط است با productivity tools نصب شده):



در پوشه‌ی Views ، دو صفحه اضافه شده‌اند که توسط user control ایی به نام menu لیست شده و راهبری خواهند شد. مونتاژ نهایی هم در MainPage.xaml صورت می‌گیرد.
کدهای XAML‌ مرتبط با منوی ساده برنامه به شرح زیر هستند (Menu.xaml) :

<UserControl x:Class="MvvmLight6.Views.Menu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MvvmLight6.ViewModels" mc:Ignorable="d"
FlowDirection="RightToLeft" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:MenuViewModel x:Key="vmMenuViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource vmMenuViewModel}}">
<HyperlinkButton Content="صفحه یک" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page1.xaml"
/>
<HyperlinkButton Content="صفحه دو" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page2.xaml"
/>
</StackPanel>
</UserControl>

کدهای ViewModel مرتبط با این View که کار Command گردانی را انجام خواهد داد به شرح زیر است:
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLight6.ViewModels
{
public class MenuViewModel
{
public RelayCommand<string> DoNavigate { set; get; }

public MenuViewModel()
{
DoNavigate = new RelayCommand<string>(doNavigate);
}

private static void doNavigate(string url)
{
Messenger.Default.Send(url, "MyNavigationService");
}
}
}

تمام آیتم‌های منوی فوق یک روال را صدا خواهند زد : DoNavigate . تنها تفاوت آن‌ها در CommandParameter ارسالی به RelayCommand ما است که حاوی آدرس قرارگیری فایل‌های صفحات تعریف شده است. این آدرس‌ها با کمک امکانات کلاس Messenger مجموعه‌ی MVVM light toolkit به View اصلی برنامه ارسال می‌گردند.
کدهای XAML مرتبط با MainPage.xaml به شرح زیر هستند:

<UserControl x:Class="MvvmLight6.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:usr="clr-namespace:MvvmLight6.Views"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="268" />
</Grid.ColumnDefinitions>
<usr:Menu Grid.Column="1" />
<sdk:Frame Margin="5"
Name="frame1"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Grid.Column="0" />
</Grid>
</UserControl>

و کار دریافت پیغام‌ها (یا همان آدرس صفحات جهت انجام راهبری) و عکس العمل نشان دادن به آن‌ها توسط کدهای ذیل صورت خواهد گرفت:
using System;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLight6
{
public partial class MainPage
{
public MainPage()
{
registerMessenger();
InitializeComponent();
}

private void registerMessenger()
{
Messenger.Default.Register<string>(this, "MyNavigationService", doNavigate);
}

private void doNavigate(string uri)
{
frame1.Navigate(new Uri(uri, UriKind.Relative));
}
}
}

ابتدا یک Messenger در اینجا رجیستر می‌شود و سپس به ازای هر بار دریافت پیغامی با token مساوی MyNavigationService ، متد doNavigate فراخوانی خواهد گردید.
کدهای این مثال را از اینجا می‌توانید دریافت کنید.

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

اگر در یک محیط کاری به برنامه نویس‌ها دقت کنید دو گروه را به وضوح می‌توان تمایز داد. کسانی که برنامه نویسی می‌کنند تا اموراتشان بگذرد و کسانی که واقعا علاقمند به کارشان و دنیای برنامه نویسی هستند. به گروه اول می‌توان IT worker نام داد و گروه دوم را Software developer نامید.
جدول ذیل تفاوت‌های این دو گروه را بر می‌شمارد:

IT Workers Software developers
عموما 5 تا 9 ساعت در یک شرکت کار می‌کنند. عموما 5 تا 9 ساعت در یک شرکت کار کرده و پس از مراجعت به منزل بر روی پروژه‌های شخصی کار می‌کنند.
با اینکه هنوز در همان شرکت مشغول به کار است همیشه مشغول نق زدن است. احتمالا شاید بتواند همان موقعیت کاری را در یک شرکت دیگر نیز کسب کند. تا زمانیکه شغل فعلی برای او جذابیت دارد به آن ادامه خواهد داد و ترسی از حضور در شرکت‌های دیگر ندارد.
تنها محل یادگیری او همان پروژه‌هایی است که در شرکت وجود دارند یا مشغول به کار بر روی آن‌ها است. دید کاری و آموزشی او تنها به همین موارد خلاصه می‌شود. به صورت مداوم مشغول خواندن بلاگ‌ها، کتاب‌های جدید و فراگیری نحوه‌ی استفاده از برنامه‌های جدید می‌باشد.
عموما و اکثریت آن‌ها فقط به خاطر کلاس کاری به این رشته روی آورده‌اند و نه اصل کار مربوطه. به شدت علاقمند به بهبود روش‌های توسعه کاری و همچنین بهبود وضعیت خویش هستند.
اگر احتمالا بلاگی داشته باشند تنها به توضیح همان نق زدن‌های رایج در محیط کار می‌پردازند. از بلاگ خود در جهت توضیح تجارب کاری و کمک به ارتقای سایر همکاران خود استفاده می‌کنند.
اگر دانشی را کسب می‌کنند تنها محل عرضه‌ی آن جهت پز دادن پیش مدیر پروژه خواهد بود. بسیار با معلومات اما افتاده حال هستند.
از تغییرات مداوم دنیای IT که در آن قرار دارند هراسان هستند. مدام نق می‌زنند که مگر فاکس پروی 2.6 چه مشکلی دارد که باید از NHibernate استفاده کنند؟!
این نوع افراد همیشه می‌گویند که وقت ندارند مطالب جدید را بیاموزند و میل به تحجر و مقاومت در برابر تغییرات در آن‌ها بسیار زیاد است.
در تغییرات روی داده در دنیای IT سهیم بوده و جزئی از آن هستند.
زمانیکه قرار است یک قطعه کد اس کیوال را نمایش دهند از یک برچسب ساده یا یک تکست باکس استفاده می‌کنند. در حدی که فقط به قولی برنامه "کار کند". در همان حدی کار می‌کنند که به آن‌ها حقوق می‌دهند. نه بیشتر. چند روز وقت می‌گذارند و با روش‌های مختلف syntax highlighting و نمایش زیبای کد آشنا می‌شوند تا کاری را که ارائه می‌دهند مزه‌ی غذای مانده‌ی چند روز قبل را ندهد.

برای مطالعه بیشتر
+ و + و +

مطالب
کمی شوخی با IE6

اخیرا عده‌ای از همکاران عنوان می‌کنند که "لطفا" از IE6 استفاده نکنید، یا یک پیغام "لطیف" را به کاربران IE6 نمایش می‌دهند و امثال آن. نظر شما در مورد استفاده از روش‌های زیر چیست؟ :-)

مثال زیر را در نظر بگیرید:

<html>
<head>
<title>crash-1</title>
<style type="text/css">
* {position: relative;}
</style>
</head>
<body>

<table><input></table>

</body>

</html>

IE6 کاربر، پس از باز کردن این صفحه، بلافاصله محو خواهد شد. به صورت خلاصه همین یک سطر زیر برای کرش IE6 کافی است:

<style>*{position:relative}</style><table><input></table>

مثال 2:
<html>
<head>
<title>crash-2</title>
<script type="text/javascript">for (x in document.write) { document.write(x);}</script>
</head>
<body>
Test-2
</body>

</html>

این یک سطر اسکریپت نیز سبب کرش IE6 خواهد شد.

مثال 3:
این مورد کمی خطرناک است و سبب هنگ کل سیستم عامل خواهد شد و همچنین مصرف شدید حافظه تا حد ممکن.
<html>
<head>
<title>crash-3</title>
<style type="text/css">
:link:hover {
color: #f00;
}
:link .images {
width: 300px;
display: none;
}
:link:hover .images {
position: absolute;
top: 10px;
left: 230px;
display: block;
}
</style>
</head>

<body>

<a href="#">Line 1
<div class="images">
Line 2: HOVERING OVER THIS LINE MAY CRASH THE BROWSER! <br>Line 3: What is happening
here?</div>
</a>

</body>

</html>

مثال 4:
این مورد نیز سبب هنگ سیستم عامل می‌شود:

<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
<head>
<style type="text/css">
a {
padding:2px;
white-space:nowrap;
position:relative;
}
</style>
</head>
<body>

<div>
<a href="#">Option 1</a>
<a href="#">Option 2</a>
</div>

</body>
</html>

و اما روش‌های محترمانه!