مطالب
معرفی Selector های CSS - قسمت 1
1- .class
این Selector تگ‌هایی را انتخاب می‌نماید که عضو یک کلاس خاص باشند.
<style>
    .first{ color: red}
    .content{color:blue}
</style>
<div class="first">Text 1</div>
<div>Text 2</div>
<p class="first">Text 3</p>
<div class="content">Text 4</div>
در مثال فوق Text 1 و Text 3 به رنگ قرمز و Text 4 به رنگ آبی نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes .class   1

2- #id
این Selector تگی را انتخاب می‌کند که دارای یک id خاص می‌باشد.
<style>
    #pass {
        color: red;
    }        
</style>
<input type="password" id="pass"/>
در مثال فوق محتوای کادر رمز عبور به رنگ قرمز نمایش می‌یابد.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes #id   1

3- E
جهت انتخاب تگ‌ها براساس عنوان آنها استفاده می‌شود.
<style>
    div {
        color: red;
    }
</style>
<div>Text 1</div>
<p>Text 2</p>
<div>Text 3</div>
در مثال فوق Text 1 و Text 3 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes  1

4- *
این Selector تمامی تگ‌ها را انتخاب می‌نماید
<style>
    * {
        color: red;
    }
</style>
<div>Text 1</div>
<input type="text" value="Text 2"/>
<select>
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
    <option>Option 4</option>
</select>
در مثال فوق رنگ متن تمامی تگهای فوق قرمز خواهد شد.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0  2

5- S1  S2
تمامی المنت‌های S2 که فرزند S1 می‌باشند، انتخاب خواهند شد.
<style>
    div span {
        color:red
    }
</style>
<div>
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>
        <span>Text 3</span>
        <div>Text 4</div>
    </p>
    <span>Text 5</span>
</div>
در مثال فوق Text 2، Text 3 و Text 5 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes S1   S2  1

6- S1>S2
تمامی المنت‌های S2 که فرزند مستقیم S1 می‌باشند، انتخاب خواهند شد. 
<style>
    div>span {
        color:red
    }
</style>
<div>
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>
        <span>Text 3</span>
        <div>Text 4</div>
    </p>
    <span>Text 5</span>
</div>
در مثال فوق Text 2 و Text 5 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  7.0 Yes   Yes S1>S2  2 

7- S1+S2
تمامی المنت‌های S2 که هم تراز S1 و دقیقا بعد از S1 قرار گرفته اند را انتخاب می‌نماید.
<style>
    h1+p {
        color: red;
    }
</style>
<div>
    <h1>Text 1</h1>
    <p>Text 2</p>
    <div>Text 3</div>
    <p>Text 4</p>
    <h1>
        <p>Text 5</p>
    </h1>
    <p>Text 6</p>
</div>
در مثال فوق Text 2 و Text 6 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  7.0 Yes   Yes S1+S2  2 

8- S1~S2
تمامی المنت‌های S2 که هم تراز S1 می‌باشند و بعد از S1 قرار گرفته اند را انتخاب می‌نماید.
<style>
    div ~ p {
        color: red;
    }
</style>
<div>
    <h1>Text 1</h1>
    <p>Text 2</p>
    <div>Text 3</div>
    <p>Text 4</p>
    <h1>
        <p>Text 5</p>
    </h1>
    <p>Text 6</p>
</div>
در مثال فوق Text 4 و Text 6 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 S1~S2  3

9- S1!>S2
این Selector المنت‌های S1 را انتخاب می‌کند که والد مستقیم S2 می‌باشند.
<style>
    div!>span {
            border: 1px solid red;
        }
</style>
<div>
    <h1>Text 1</h1>
    <div>
        <span>Text 2</span>
        <h1>Text 3</h1>
        <p>Text 4</p>
    </div>
    <div>Text 5</div>
</div>
در مثال فوق کادری قرمز رنگ را دور آن div که شامل محتوای Text 2، Text 3 و Text 4 می‌باشد، ترسیم خواهد نمود.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No  No No
No
No S1!>S2  4

 10- S1 /attribute/ S2
تمامی المنت‌های S2 انتخاب می‌شوند که توسط یک attribute از S1 به id المنت S2 ارجاع داده است.
<style>
    label /for/ input {
        color: red;
    }
</style>
<label for="user">User Name:</label>
<input type="text" id="user"/>
<label>Password:</label>
<input type="password" id="pass"/>
در مثال فوق، اولین تگ input که id آن user می‌باشد و توسط تگ label و با استفاده از ویژگی for (به id تگ input) ارجاع داده شده است، انتخاب و محتوای آن قرمز می‌شود.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No  No No
No
No S1 /attribute/ S2  4
 
مطالب
فلسفه وجودی بخش finally در try catch چیست؟
حتما شما هم متوجه شدید که وقتی رخداد یک استثناء را با استفاده از try و catch کنترل می‌کنیم، هر چیزی که بعد از بسته شدن تگ catch بنویسیم، در هر صورت اجرا می‌شود.

 try {
     int i=0;
     string s = "hello";
     i = Convert.ToInt32(s);
} catch (Exception ex)
{
     Console.WriteLine("Error");
}
Console.WriteLine("I am here!");

پس فلسفه استفاده از بخش finally چیست؟
در قسمت finally منابع تخصیص داده شده در try را آزاد می‌کنیم. کد موجود در این قسمت به هر روی اجرا می‌شود چه استثناء رخ دهد چه ندهد. البته اگر استثناء رخ داده شده در لیست استثناء هایی که برای آنها catch انجام دادیم نباشد، قسمت finally هم عمل نخواهد کرد مگر اینکه از catch به صورت سراسری استفاده کنیم.
اما مهمترین مزیتی که finally ایجاد می‌کند در این است که حتی اگر در قسمت try با استفاده از دستوراتی مثل return یا break یا continue از ادامه کد منصرف شویم و مثلا مقداری برگردانیم، چه خطا رخ دهد یا ندهد کد موجود در finally اجرا می‌شود در حالی که کد نوشته شده بعد از try catch finally فقط در صورتی اجرا می‌شود که به طور منطقی اجرای برنامه به آن نقطه برسد. اجازه بدهید با یک مثال توضیح دهم. اگر کد زیر را اجرا کنیم:
  public static int GetMyInt()
 {
     try {
          for (int i=10;i>=0;i--)
             Console.WriteLine(10/i);
         return 1;
     } catch
     {
         Console.WriteLine("Error!");
     }
     finally {
         Console.WriteLine("ok");
     }
     Console.WriteLine("can you reach here?");  
     return -1;  
 }

برنامه خطای تقسیم بر صفر می‌دهد اما با توجه به کدی که نوشتیم، عدد -1 به خروجی خواهد رفت. در عین حال عبارت ok و can you reach here در خروجی چاپ شده است. اما حال اگر مشکل تقسیم بر صفر را حل کنیم، آیا باز هم عبارت can you reach here در خروجی چاپ خواهد شد؟
 
public static int GetMyInt()
 {
     try {
          for (int i=10;i>=1;i--)
             Console.WriteLine(10/i);
         return 1;
     } catch
     {
         Console.WriteLine("Error!");
     }
     finally {
         Console.WriteLine("ok");
     }
     Console.WriteLine("can you reach here?");  
     return -1;  
 }

مشاهده می‌کنید که مقدار 1 برگردانده می‌شود و عبارت can you reach here در خروجی چاپ نمی‌شود ولی همچنان عبارت ok که در finally ذکر شده در خروجی چاپ می‌شود. یک مثال خوب استفاده از چنین وضعیتی، زمانی است که شما یک ارتباط با بانک اطلاعاتی باز می‌کنید، و نتیجه یک عملیات را با دستور return به کاربر بر می‌گردانید. مسئله این است که در این وضعیت چگونه ارتباط با دیتابیس بسته شده و منابع آزاد می‌گردند؟ اگر در حین عملیات بانک اطلاعاتی، خطایی رخ دهد یا ندهد، و شما دستور آزاد سازی منابع و بستن ارتباط را در داخل قسمت finally نوشته باشید، وقتی دستور return فراخوانی می‌شود، ابتدا منابع آزاد و سپس مقدار به خروجی بر می‌گردد.
public int GetUserId(string nickname)
{
     SqlConnection connection = new SqlConnection(...);
     SqlCommand command = connection.CreateCommand();
     command.CommandText = "select id from users where nickname like @nickname";
     command.Parameters.Add(new SqlParameter("@nickname", nickname));
      try {
         connection.Open();
          return Convert.ToInt32(command.ExecuteScalar());
      } 
      catch(SqlException exception)
      {
      // some exception handling
         return -1;
      } finally {
          if (connection.State == ConnectionState.Open)
         connection.Close();
      }
      // if all things works, you can not reach here
}


مطالب
نحوه‌ی خواندن مقادیر Query String با استفاده از جاوااسکریپت

در این مقاله، به نحوه‌ی دریافت مقادیر Query String با استفاده از زبان جاوااسکریپت خواهیم پرداخت. گاها در پروژه‌ها نیاز است تا کاربر را با پارامترهایی به صورت query string، به صفحه‌ای دیگر منتقل کنیم. با این حال به دو صورت می‌توان این مقادیر را فراخوانی نمود:

  1. فراخوانی سمت سرور
  2. فراخوانی سمت کلاینت

زمانیکه صحبت از کد نویسی سمت کلاینت می‌شود، گزینه‌ی بهتری بجز JavaScript وجود ندارد؛ که البته بسیار کارا و پر کاربرد است.

برای دریافت query string با زبان جاوااسکریپت می‌توان از کد زیر استفاده کرد: 

 <script>
    (function () {
        // we can call getQueryStringByName from anywhere we want
        var result = getQueryStringByName('id');
        alert(result);
    })();

    function getQueryStringByName(name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(location.search);
        return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }
</script>

getQueryStringByName:

با استفاده از این متد می‌توان به query string دسترسی یافت. تابع getQueryStringByName حاوی یک ورودی می‌باشد که باید نام query string در آن قرار گیرد. در مثال بالا نام query string برابر است با language که بعد از اجرای پروژه، مقدار آن به صورت دیالوگ نمایش داده می‌شود. 

برای دانلود پروژه اینجا کلیک کنید. 

مطالب
کارهایی جهت بالابردن کارآیی Entity Framework #3

در قسمت‌های قبلی (^ و ^) راهکارهایی جهت بالا بردن کارآیی، ارائه شد. در ادامه، به آخرین قسمت این سری اشاره خواهم کرد.

فراخوانی متد شناسایی تغییرات

یادآوری: قبل از هر چیز با توجه به این مقاله دانستن این نکته الزامی است که فراخوانی برخی متدها مانند DbSet.Add سبب فراخوانی DataContext.ChangeTracker.DetectChanges خواهند شد.

فرض کنید قصد افزودن 2000 موجودیت دانش آموز را دارید:

for (int i = 0; i < 2000; i++)
{
    Pupil pupil = GetNewPupil();
    db.Pupils.Add(pupil);
}
db.SaveChanges();
در کد بالا بدلیل فراخوانی متد DbSet.Add و به دنبال آن فراخوانی متد DetectChanges در هر بار اجرای حلقه (2000بار) مدت زمان اجرای کد بالا بسیار زیاد است و اگر به پروفایلر نگاهی بیاندازید، اشغال CPU توسط کوئری بالا، بیش از حد متعارف است.

اگر به تصویر بالا دقت کنید بیش از 34 ثانیه (خط 193 قسمت سوم شکل) جهت افزودن 2000 موجودیت به کانتکست سپری شده است. در حالی که درج این 2000 موجودیت کمی بیش از 1 ثانیه (خط 195 قسمت سوم شکل) که 379 میلی ثانیه (قسمت دوم شکل) آن مربوط به اجرای کوئری اختصاص یافته  طول کشیده است.
بیشترین زمان صرف شده‌ی برای درج 2000 موجودیت، در کد برنامه سپری شده است که با بررسی بیشتر در پروفایلر، متوجه زمان بر بودن فراخوانی متد ()DetectChanges که در فضای نام Data.Entity.Core وجود دارد خواهید شد. این متد 2000 بار به تعداد موجودیت هایی که قصد داریم به بانک اطلاعاتی اضافه نماییم، فراخوانی شده است.

همانطور که در شکل بالا مشخص است همان 34 ثانیه جهت ردیابی تغییرات صرف شده است. EF ردیابی تغییرات را بصورت پیش فرض هر زمانی که قصد افزودن یا ویرایش موجودیتی را داشته باشید، انجام خواهد داد و هر چه موجودیت‌های بیشتری را بخواهید ویرایش یا اضافه نمایید، این زمان نیز بیشتر خواهد شد. در حقیقت زمان لازم برای الگوریتم ردیابی تغییرات بصورت نمایی با رشد موجودیت‌ها افزوده می‌شود. به عبارت دیگر اگر این تعداد موجودیت‌ها را به 4000 عدد برسانید، مدت زمان لازم بیش از 2 برابر افزوده خواهد شد.

راه حل اول:
استفاده از متد ()AddRange ارائه شده در EF6 که جهت درج دسته‌ای (Bulk Insert) ارائه شده است:
var list = new List<Pupil>();

for (int i = 0; i < 2000; i++)
{
    Pupil pupil = GetNewPupil();
    list.Add(pupil);
}

db.Pupils.AddRange(list);
db.SaveChanges();

راه حل دوم:

در سناریوهای پیچیده، مانند درج دسته‌ای چندین موجودیت، شاید مجبور به خاموش نمودن این قابلیت شوید:
db.Configuration.AutoDetectChangesEnabled = false;
توجه داشته باشید اگر قصد دارید از امکان ردیابی تغییرات مجددا بهره‌مند شوید، باید این قابلیت را نیز فعال نمایید. با خاموش نمودن ردیابی تغییرات بار دیگر کوئری ابتدایی را اجرا نمایید. مدت زمان لازم جهت افزودن 2000 موجودیت به کانتکست از بیش از 34 ثانیه به 85 میلی ثانیه کاهش یافته است؛ ولی اعمال تغییرات به بانک اطلاعاتی همانند مرتبه اول بیش از 1 ثانیه طول خواهد کشید.


ردیابی تغییرات

هنگامی که موجودیتی را از بانک اطلاعاتی دریافت نمایید، می‌توانید آن را ویرایش نمایید و مجددا به بانک اطلاعاتی اعمال نمایید. چون EF اطلاعی از قصد شما برای موجودیت نمی‌داند، مجبور است تغییرات شما را زیر نظر بگیرد که این زیر نظر گرفتن، هزینه و سربار دارد و این سربار و هزینه برای داده‌های زیاد، بیشتر خواهد شد. بنابراین اگر قصد دارید اطلاعاتی فقط خواندنی را از بانک اطلاعاتی دریافت نمایید، بهتر است صراحتا به EF بگویید این موجودیت را تحت ردیابی قرار ندهد.
string city = "New York";

List<School> schools = db.Schools
    .AsNoTracking()
    .Where(s => s.City == city)
    .Take(100)
    .ToList();

استفاده از متد AsNoTracking  در کد بالا سبب خواهد شد 100 مدرسه که در شهر نیویورک وجود دارد توسط EF، بدون تحت نظر گرفتن آن‌ها از بانک اطلاعاتی دریافت شوند و سرباری نیز تحمیل نشود.

ویوهای از قبل کامپایل شده

معمولا، هنگامی که از EF برای اولین بار استفاده می‌نمایید، ویوهایی ایجاد می‌گردد که برای ایجاد کوئری‌ها مورد استفاده قرار می‌گیرند. این ویوها در طول حیات برنامه فقط یکبار ایجاد می‌شوند. ولی همین یکبار هم زمانبر هستند. خوشبختانه راه‌هایی وجود دارد که ایجاد این ویوها را در زمان runtime انجام نداد و آن راه، استفاده از ویوهای از پیش کامپایل شده است. یکی از راههای ایجاد این ویوها استفاده از Entity Framework Power Tools است. بعد از نصب اکستنشن، بر روی فایل کانتکست راست کلیک کرده و سپس گزینه‌ی Generate Views را از منوی Entity Framework انتخاب کنید.

توجه داشته باشید که هر تغییری را بعد از ایجاد این ویوها بر روی کانتکست اعمال نمایید، باید آن‌ها را مجددا تولید کنید. برای آشنایی بیشتر با این ویوها به این لینک مراجعه کنید. هم چنین پکیج نیوگتی بنام EFInteractiveViews نیز برای این منظور تهیه و توزیع شده است.


حذف کوئری‌های ابتدایی غیر ضروری

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

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

 public class CustomManifestTokenResolver : IManifestTokenResolver
{
    public string ResolveManifestToken(DbConnection connection)
    {
        return "2014";
    }
}
و توسط کلاسی که از کلاس DbConfiguration ارث بری کرده است آن را استفاده نماییم.
 public class CustomDbConfiguration : DbConfiguration
{
    public CustomDbConfiguration()
    {
        SetManifestTokenResolver(new CustomManifestTokenResolver());
    }
}


تخریب کانتکست

تخریب و از بین بردن کانتکست هنگامی که به آن نیاز نداریم بسیار ضروری است. یکی از روشهای اصولی برای Disposing کانتکست، محصور کردن آن بوسیله دستور Using است (البته فرض بر این است که قرار نیست از الگوهای اشاره شده استفاده نماییم). در صورت عدم تخریب صحیح کانتکست باید منتظر آسیب جدی به کارایی Garbage Collector جهت آزاد سازی منابع مورد استفاده کانتکست و هم چنین باز نمودن اتصالات جدید به دیتابیس باشید.


پاسخگویی به چندین درخواست بر روی یک کانکشن

EF از قابلیتی بنام Multiple Result Sets می‌تواند بهره ببرد که این قابلیت باعث می‌شود بر روی یک کانکشن ایجاد شده، یک یا چند درخواست از دیتابیس ارسال و یا دریافت شود که سبب کاهش تعداد رفت و برگشت به دیتابیس می‌شود. کاربرد دیگر این قابلیت، زمانی است که تاخیر زیادی (latency) بین اپلیکیشن و دیتابیس وجود دارد.

برای فعالسازی کافی است مقدار زیر را در کانکشن استرینگ اضافه نمایید:

MultipleActiveResultSets=True;


استفاده از متدهای ناهمگام

در C#5 و EF6 پشتیبانی خوبی از متدهای ناهمگام فراهم شده است و اکثر متدهایی مانند ToListAsync, CountAsync, FirstAsync, SaveChangesAsync و غیره که باعث رفت و برگشت به دیتابیس می‌شوند امکان پشتیبانی ناهمگام را نیز دارند. البته این قابلیت برای برنامه‌های یک درخواست در یک زمان شاید مفید نباشد؛ ولی برای برنامه‌های وبی برعکس. در برنامه وب جهت پشتیبانی از بارگذاری همزمان (concurrent) قابلیت ناهمگام (Async) سبب خواهد شد منابع تا زمان اجرای کوئری به ThreadPool بازگردانده شود و برای سرویس دهی مهیا باشند که باعث افزایش scalability خواهد شد.

بررسی و آزمایش با داده‌های واقعی

در اکثر مواقع کارآیی با حجیم شدن داده‌ها کاهش پیدا می‌کند (البته در صورت عدم رعایت اصول استاندارد). بنابراین بررسی کارآیی در محیط هایی با حجم داده‌های بالا ضروری است. هیچ چیز بدتر از آن نیست که همه چیز در محیط توسعه خوب و بی نقص باشد ولی در محیط عملیاتی به شکست بیانجامد. به همین جهت سعی کنید از ابزارهای تولید داده (^ و ^ و ^) برای ایجاد داده‌های آزمایشی استفاده نمایید. سپس کارآیی کوئری خود را مورد بررسی و آزمایش قرار دهید.

مطالب
آشنایی با ساختار IIS قسمت پنجم
در مطالب قبلی در مورد ماژولار بودن IIS زیاد صحبت کردیم، ولی اجازه بدهید این مورد را به صورت کاربردی‌تر و موشکافانه‌تر بررسی کنیم. برای اینکه به مشکلی در طول این سری از مطالب برنخورید، IIS را به صورت کامل یعنی full feature نصب نمایید. از بخش control panel>programs & features>Turn Windows features on or off اقدام نمایید و هرچه زیر مجموعه Internet information service هست را برگزینید. در صورتی که از نسخه‌های ویندوز سرور 2008 استفاده می‌کنید از طریق server manager>roles>web server اقدام نمایید.
برای نصب یک ماژول باید دو مرحله را انجام داد:
  1. نصب ماژول
  2. فعال سازی ماژول
نکته ای که در مورد ماژول‌های native وجود دارد این هست که این ماژول‌ها دسترسی بدون محدودیتی به منابع سروری دارند و از این رو حتما باید این نکته را دقت کنید که ماژول native شما از یک منبع مورد اعتماد دریافت شده باشد.

نصب یک native module
برای نصب می‌توانید یکی از سه راه زیر را استفاده کنید:
  1. ویرایش دستی فایل کانفیگ و از نسخه IIS7.5 به بعد هم می‌توانید از configuration editor هم استفاده کنید.
  2. استفاده از محیط گرافیکی IIS
  3. استفاده از خط فرمان با دستور Appcmd
مزیت روش دستی این هست که شما دقیقا می‌دانید در پشت صحنه چه اتفاقی می‌افتد و نتیجه هر کدام از این سه روش، اضافه شدن یک مدخل ورودی به تگ <globalmodules> است. برای اعمال تغییرات، مسیر زیر را بروید:
%windir%\system32\inetsrv\config\applicationhost.config
کسی که نیاز به دسترسی به این مسیر و انجام تغییرات دارد باید در بالاترین سطح مدیریتی سرور باشد.
اگر فایل را باز کنید و تگ globalmodule را پیدا کنید متوجه می‌شوید که تمامی ماژول‌ها در این قسمت معرفی شده‌اند و برای خود یک مدخل ورودی یا همان تگ add را دارند که در آن مسیر فایل dll هم ذکر شده است:
<globalModules> 
 
 <addname="DefaultDocumentModule"image="%windir%\system32\inetsrv\defdoc.dll"/> 
 
 <addname="DirectoryListingModule"image="%windir%\system32\inetsrv\dirlist.dll"/> 

<add name="StaticFileModule"image="%windir%\system32\inetsrv\static.dll"/>
...

 </globalModules>

برای حذف یا جایگزینی یک ماژول به راحتی می‌توانید مدخل ورودی یک ماژول را به صورت دستی حذف نمایید و برای جایگزینی هم بعد از حذف، ماژول خود را معرفی کنید. ولی توجه داشته باشید که این حذف به معنی حذف این ماژول از تمامی اپلیکیشن‌های موجود بر روی IIS هست و سپس اضافه کردن یک ماژول به این بخش. همچنین اگر قصد شما فقط حذف یک ماژول از روی یکی از اپلیکشن‌ها باشد باید از طریق فایل کانفیگ سایت از مسیر تگ‌های <system.webserver><modules> و با استفاده از تگ‌های add و remove به معرفی یک ماژول مختص این اپلیکیشن و یا حذف یک ماژول خاص اقدام نمایید.

PreConditions
این ویژگی می‌تواند در خط معرفی ماژول، مورد استفاده قرار بگیرد. اگر به فایل نگاه کنید می‌بینید که در بعضی خطوط این ویژگی ذکر شده است. تعریف این ویژگی به هسته IIS می‌گوید که این ماژول در چه مواردی و به چه شیوه ای باید به کار گرفته شود.
 <add name="ManagedEngine64" image="%windir%\Microsoft.NET\Framework64\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness64" /> 
 
 <add name="ManagedEngine" image="%windir%\Microsoft.NET\Framework\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness32" /> 
 
<add name="ManagedEngineV4.0_32bit" image="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness32" /> 
 
<add name="ManagedEngineV4.0_64bit" image="%windir%\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness64" />

مقادیری که precondition میتواند بگیر شامل موارد زیر هستند:
bitness
این گزینه به دو صورت bitness32 و bitness64 یافت می‌شود. امروزه پردازنده‌های 64 بیتی بسیار متداول شده اند و بسیاری از تولید کنندگان دارند به سمت عرضه ابزارهای 64 بیتی رو می‌آورند و به زودی عرضه‌های 32 بیتی را متوقف می‌کنند و به سمت سیستم عامل‌های 64 بیت سوییچ خواند کرد ولی باز هم هنوز برنامه‌های 32 بیتی زیادی هستند که مورد استفاده قرار می‌گیرند و نمی‌توان آن‌ها را نادیده گرفت. برای همین ویندوزهای 64 بیتی مایکروسافت در کنار محیط 64 بیتی‌شان از یک محیط 32 بیت به اسم WOW64 استفاده می‌کنند. در این حالت این امتیاز به شما داده می‌شود که از پروسه‌های کارگر 32 بیتی در کنار پروسه‌های کارگر 64 بیتی استفاده کنید و PreCondition به bitness32 یا bitness64 تنظیم می‌شود تا از صحت بارگزاری dll در یک محیط درست مطمئن شود. در صورتی که این خصوصیت ذکر نشود یک هندلر 32 بیتی و 64 بیتی و یک module map اجرا می‌شود.

Runtime version
اگر ماژول خاصی برای اجرا به ورژن خاصی از net framwork. نیاز دارد، این ویژگی ذکر می‌شود. در صورتی که ماژولی قصد اجرای بر روی فریم ورک اشتباهی داشته باشد سبب خطا خواهد شد.

Mana gedHandler 
با معرفی IIS7 ما با یک مدل توسعه پذیر روبرو شدیم و می‌توانستیم ماژول‌ها و هندلرهای خود را بنویسیم و مستقیما در Pipeline قرار دهیم ولی سوییچ کردن بین دو بخش کدهای مدیریت شده و native یک عمل سنگین برای سیستم به شمار می‌آید و به منظور کاهش این بار گزینه managedhandler قرار داده شده است تا تعیین کند مواقعی که درخواست نیازی به این ماژول ندارد، این ماژول اجرا نگردد. به عنوان مثال فایل‌های ایستا چون jpg یا html و... شامل این ماژول نخواهند شد. واضح‌ترین مثال در این زمینه Forms Authentication می‌باشد و موقعی اجرا می‌شوند که درخواست فایل‌های aspx شده باشد و اگر یک فایل html را درخواست کنید این ماژول امنیتی روی آن اثری ندارد و عملیات شناسایی هویت روی آن اجرا نمی‌شود و اگر می‌خواهید روی همه فایل‌ها، این عملیات شناسایی انجام شود باید خصوصیت "precondition="managedhandler حذف شود.
در صورتی که تگ module را به صورت زیر بنویسید تمامی ماژول‌ها برای تمامی درخواست‌ها اجرا خواهد شد، صرف نظر از اینکه آیا ماژولی به صورت "precondition="managedmodule مقداردهی شده است یا خیر.
<modules runAllManagedModulesForAllRequests="true"/>
 
The Mode Precondition 
تا به الان گفتیم که چگونه میتوانیم یک ماژول و یا هندلر مدیریت شده‌ای را به Pipeline اضافه کنیم؛ ولی IIS7 به بالا نیاز دارد که تا پروسه‌های کارگر را به روشی خاص به این منظور اجرا کند و فریم ورک دات نت را برای اجرای آن‌ها بارگزاری کند. همچنین به اجرای ماژولی به اسم webengine.dll برای مدیریت  ماژولهای مدیریت شده نیازمند است و خود IIS در مورد کدهای مدیریت شده چیزی متوجه نمی‌شود. پس ما برای اینکه IIS را متوجه این موضوع نمائیم، باید Integrated mode را به آن معرفی کنیم.
در نسخه‌های قبلی IIS یک روش قدیمی برای کدهای مدیریت شده وجود داشت که از طریق اینترفیسی به نام ISAPI صورت می‌گرفت. در این حالت ASPNET_ISAPI.DLL مسئول این کار بود و اگر هنوز هم میخواهید از این dll در نسخه‌های جدیدتر IIS کمک بگیرید باید به جای معرفی classic mode ، integration mode را معرفی کنید.
با برابر کردن precondtion به مقدار "intgretedmode" هندلر یا ماژول شما در یک پول با خصوصیت integrated بارگزاری خواهد شد و اگر مقدار آن "classicmode" باشد در یک پول بدون خاصیت integrated بارگزاری می‌شود.
تفاوت بین دو روش کلاسیک و مجتمع integrated بر سر این هست که در روش جدید، ماژول شما به عنوان یک پلاگین برای IIS دیده نمی‌شود و کد شما را جزئی از کامپوننت‌های خود به شمار می‌آورد. به صورت واضح‌تر در حالت کلاسیک موقعی که درخواستی وارد pipeline میشد ابتدا از کامپوننت‌ها و ماژول‌های داخلی خود IIS عبور داده میشد و بعد فایل ASPNET_ISAPI.DLL جهت پردازش کدهای مدیریت شده صدا زده میشد و با توجه به کدهای شما، بعضی مراحل تکرار میشد؛ مثلا اگر کد شما در مورد Authentication بود و بعد از گذر از مراحل auth داخل خود IIS و بقیه موارد دوباره نوبت کد شما و گذر از مراحل authentication بود. یعنی وجود دو pipeline؛ ولی در حالت مجتمع این دوبار انجام وظیفه از بین رفته است چرا که کدهای شما به طور مستقیم در pipeline قرار دارند و آن‌ها را جزئی از خود می‌داند، نه یک پلاگین که افزون بر فعالیت خودشان، اجرای کدهای شما رو هم بر دوش بکشند.
شکل زیر نمونه ای از حالت کلاسیک را نشان می‌دهد که در آن دو بار عمل auth دارد انجام میگیرد.
 



شکل زیر هم نمونه ای حالت مجتمع هست:

 
در کل امروزه دیگر استفاده از روش کلاسیک راهکار درستی نیست و این ویژگی تنها به عنوان یک سازگاری با نمونه کارهای قدیمی است.
تگ‌هایی که از خصوصت precondition استفاده می‌کنند به شرح زیر هستند:
  • ISAPI filters 
  • globalModules 
  • handlers 
  • modules 

در مورد بقیه تگ‌ها در آینده بیشتر بحث می‌کنیم. بهتر هست مطلب را با توضیح precondition جهت ممانعت از طولانی و طومار شدن در اینجا ببندیم و در قسمت آینده دیگر روش‌های نصب ماژولمان را دنبال کنیم.
مطالب
توسعه‌ی Micro Frontends با Webpack
Micro Frontend چیست؟

micro frontend یک الگوی معماری (architecture pattern) می‌باشد؛ جایی که یک front-end app، به چند app  کوچکتر تقسیم می‌شود و هر کدام از آن‌ها به صورت مستقل  توسعه داده و تست می‌شوند.  مفهومی شبیه به مایکروسرویس‌ها است؛ اما برای سورس کد‌های یکپارچه‌ی سمت کلاینت. 


چرا؟
خیلی سخت است که بخواهیم روی سورس کد‌های یکپارچه سمت کلاینت تست نویسی، به‌روز رسانی و هم چنین نگهداری کنیم. این در حالی است که توانایی تیم را به منظور مستقل کار کردن بر روی بخش‌های مختلفی از app، محدود می‌کند. شکستن یک app یکپارچه به micro frontend‌های کوچکتر و قابل مدیریت، این امکان را فراهم میسازد که چندین تیم، به صورت مستقل کار کنند و از فریم ورک‌های ترجیحی خود استفاده کنند.

چگونه؟
اساسا 3 راه برای ادغام کردن ماژول‌های micro frontend  با container app وجود دارد. 

 1-Server integration

هر micro frontend در یک وب سرور  احتمالا جداگانه هاست شده‌است که مسئول رندر کردن و خدمت دادن markup‌های مربوطه می‌باشد. به محض دریافت درخواستی از سمت مرورگر، container app در خواست را برای markup، با برقراری تماس به سرور، برای micro frontend مربوطه انجام میدهد. 
این یک روش ایده آل نمی‌باشد؛ به‌طوریکه چندین تماس به سرور برای رندر کردن محتوا در صفحه برقرار می‌شود و  همچنین مستلزم پیاده سازی استراتژی cache، به منظور کاهش تاخیر است. 

2-Compile time integration

container app دسترسی به کد‌های micro frontend‌ها را در طول توسعه و زمان کامپایل دارد. یکی  از راه‌های انجام این روش این است که micro frontend‌ها را به عنوان بسته‌های npm منتشر کنیم و سپس از آن‌ها به عنوان یک وابستگی در container app استفاده کنیم. 
در حالیکه راه اندازی و پیاده سازی، در این حالت ساده است، اما یک وابستگی محکم بین container app  و micro frontend وجود دارد. هر زمانکه یک micro frontend بروزرسانی می‌شود، نیاز است که container app ،  به منظور یکپارچه کردن بروز رسانی، دوباره استقرار یابد.

3-Run time integration

container app دسترسی به کد‌های micro frontend‌ها را زمانیکه در مرورگر اجرا می‌شوند، دارد. یکی از راه‌های انجام این روش، استفاده از پلاگین Module Federation مربوط به Webpack است که مراقب ساختن، در دسترس قرار دادن و استفاده از وابستگی‌ها در زمان اجرا می‌باشد (در ادامه، این حالت را با جزئیات بیشتری مورد بررسی قرار خواهیم داد). 
در این حالت وابستگی بین container app و micro frontend‌ها وجود ندارد و یکپارچگی در زمان اجرا اتفاق می‌افتد. هر micro frontend می‌تواند به صورت مستقل توسعه داده شود و استقرار یابد، بدون اینکه container app را دوباره استقرار دهیم.
در این حالت راه اندازی در مقایسه با compile-time integration پیچیده‌تر است.



Webpack Module Federation Plugin

پلاگین module federation در Webpack 5.0 معرفی شد که  امکان توسعه app‌های micro frontend را با بارگذاری پویای کد‌های app‌های micro frontend را در container app ، فراهم میسازد. 
همچنین این امکان را فراهم می‌کند که dependency ها را بین remote app‌ها و host app، به منظور جلوگیری از کد‌های تکراری و کاهش دادن سایز build، به اشتراک بگذاریم.
 در این مقاله ما یک مثال را بررسی خواهیم کرد که شامل دو micro frontend ساده می‌باشد و سپس آن‌ها را در یک host app ادغام می‌کنیم. 
ساختار نهایی به‌صورت زیر خواهد بود:



ایجاد کردن اولین Remote app


یک پوشه را به نام remote1 ایجاد کنید و سپس یک فایل را به نام package.json، در پوشه‌ی ایجاد شده، با دستور زیر ایجاد کنید. 
npm init --yes
پوشه‌ی ایجاد شده را در code editor خود باز کنید (من از vs code استفاده می‌کنم) و سپس وابستگی‌های زیر را به فایل package.json اضافه کنید. 
npm install html-webpack-plugin webpack webpack-cli webpack-dev-server --save

1) webpack/ webpack-CLI: برای استفاده از webpack و دستورات webpack CLI
2) html-webpack-plugin/webpack-devserver: سرور توسعه محلی با live reloading

 و همچنین اسکریپت webpack serve را در بخش scripts، به منظور serve کردن application در مرورگر اضافه کنید.
اکنون فایل package.json شما همانند زیر می‌باشد: 
{
  "name": "remote1",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "html-webpack-plugin": "^5.3.2",
    "webpack": "^5.57.0",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.3.1"
  }
}

در ادامه، یک پوشه‌ی جدید را به نام public  ایجاد کنید و یک فایل را به نام index.html در پوشه‌ی public اضافه کنید سپس HTML markup زیر را درون فایل index.html اضافه کنید، که شامل یک div نگهدارنده می‌باشد، جائیکه خروجی app در زمان توسعه، در آن قرار میگیرد. 
<!DOCTYPE html>
<html>

<head>
    <title>
        Remote1(Localhost:7001)
    </title>
</head>

<body>
    <div id="dev-remote1"></div>
</body>

</html>

در ادامه یک پوشه‌ی جدید را به نام src  ایجاد کنید و دو فایل را با نام‌های index.js و startup.js در آن قرار دهید. در ادامه به این دو فایل برمیگردیم و کد‌های لازم را در آن قرار میدهیم.
یک فایل جدید را به نام webpack.config.js در ریشه‌ی پروژه ایجاد می‌کنیم که در آن تنظیمات webpack  را قرار میدهیم. در این فایل، دو پلاگین را به نام‌های Webpack و Module Federation، اضافه می‌کنیم.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  devServer: {
    port: 7001,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote1',
      filename: 'remoteEntry.js',
      exposes: {
        './RemoteApp1': './src/startup',
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

فایل remoteEntry.js شامل یک لیست از فایل‌های در معرض قرار داده شده‌ی توسط remote app به همراه مسیر‌های آنها می‌باشد. از این فایل در زمان ادغام کردن remote app در host app استفاده می‌شود.  
index.js نقطه‌ی ورودی remote app ما می‌باشد.  به منظور جلوگیری از eager loading، کد‌های startup را در یک js فایل جداگانه به نام startup.js قرار میدهیم. از این رو فایل index.js  شامل فقط یک import می‌باشد. 
import('./startup');
در درون فایل startup.js ، یک تابع به نام mount را export می‌کنیم. این تابع یک element را دریافت می‌کند؛ جائیکه قرار است خروجی app در آن قرار گیرد. در حالت development، خروجی در یک div نگهدارنده با شناسه dev-remote1 در فایل محلی index.html قرار میگیرد.
const mount = (el) => {
    el.innerHTML = '<div>Remote 1 Content</div>';
  };
  
  if (process.env.NODE_ENV === 'development') {
    const el = document.querySelector('#dev-remote1');
    if (el) {
      mount(el);
    }
  }
  
  export { mount };
فایل‌ها را ذخیره کنید و سپس دستور npm start را جهت مشاهده‌ی خروجی اجرا کنید. در اینجا برنامه بر روی پورت 7001 اجرا می‌شود . ( http://localhost:7001  )


ایجاد کردن دومین Remote app  


در اینجا نیز مراحل، دقیقا شبیه به ایجاد remote app قبلی می‌باشد. یک پوشه را به نام remote2 در کنار پوشه‌ی remote1 ایجاد کنید و یک فایل را به نام package.json، با وابستگی‌های معرفی شده ایجاد کنید و سپس دستور npm install را بزنید تا وابستگی‌ها نصب شوند.
{
  "name": "remote2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "html-webpack-plugin": "^5.3.2",
    "webpack": "^5.57.0",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.3.1"
  }
}
سپس یک فایل را با نام index.html و با محتوای زیر، در پوشه‌ی public ایجاد کنید:
<!DOCTYPE html>
<html>

<head>
  <title>
    Remote2(Localhost:7002)
  </title>
</head>

<body>
  <div id="dev-remote2"></div>
</body>

</html>

در ادامه یک فایل را با نام webpack.config.js در ریشه‌ی پروژه‌ی remote2 ایجاد کنید و محتوای زیر را در آن قرار دهید. مطمئن باشید که پورت اجرایی برنامه 7002 می‌باشد.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  devServer: {
    port: 7002,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote2',
      filename: 'remoteEntry.js',
      exposes: {
        './RemoteApp2': './src/startup',
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

فایل فایل index.js برای remote2
import('./startup');
فایل startup.js  برای remote2
const mount = (el) => {
    el.innerHTML = '<div>Remote 2 Content</div>';
  };
  
  if (process.env.NODE_ENV === 'development') {
    const el = document.querySelector('#dev-remote2');
    if (el) {
      mount(el);
    }
  }
  
  export { mount };

اکنون می‌توانید با اجرای دستور npm start برنامه را بر روی پورت 7002 اجرا کنید . ( http://localhost:7002 ) 

ایجاد کردن Host app و یکپارچه کردن آن با Remote app  ها


 یک پوشه‌ی جدید را به نام container در کنار دو پوشه‌ی قبلی (remote1, remote2) ایجاد کنید. در پوشه‌ی ایجاد شده، یک فایل را به نام package.json ایجاد کنید و محتوای زیر را در آن قرار دهید و سپس دستور npm install را بزنید تا لیست وابستگی‌ها دریافت شود. 
{
  "name": "container",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "html-webpack-plugin": "^5.3.2",
    "webpack": "^5.57.0",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.3.1"
  }
}

اکنون یک پوشه را به نام public، در ریشه‌ی پروژه ایجاد کنید و یک فایل را به نام index.html، در آن قرار دهید. در این فایل دو div  نگهدارنده به منظور هاست کردن محتوا، از هر remote app  قرار دارد.
<!DOCTYPE html>
<html>
  <head> 
    <title>Host App (Localhost:7000)</title>
  </head>
  <body>
    <div id="remote1-app"></div>
    <div id="remote2-app"></div>
  </body>
</html>
یک پوشه به نام src، در ریشه پروژه ایجاد کنید و دو فایل را با نام‌های index.js و startup.js، در آن قرار دهید. کمی جلوتر به این دو فایل می‌پردازیم. 

یک فایل را با نام  webpack.config.js، با تنظیمات زیر در ریشه‌ی پروژه قرار دهید. در اینجا پورت اجرایی برنامه، 7000 تعیین شده‌است:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  devServer: {
    port: 7000,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        remote1: 'remote1@http://localhost:7001/remoteEntry.js',
        remote2: 'remote2@http://localhost:7002/remoteEntry.js',
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

برای خصوصیت remotes در پلاگین Module Federation باید remote appهای را تعریف کنیم که container app می‌خواهد به آن دسترسی داشته باشد. خصوصیت remotes شامل زوج کلید/مقدارها  (key/value) می‌باشد و در اینجا کلید، رشته و مقدار هم، رشته است. مقدار (value) برای کلید (key) با نام remote app شروع میشود که آن را در بخش تنظیمات پلاگین module federation مربوط به remote app مربوطه قرار داده‌ایم. سپس @ قرار میگیرد و در ادامه آن، آدرس جایی را که remoteEntry.js مربوط به remote app  قرار دارد، می‌نویسیم.

 
 
دقیقا مثل remote app ها، در فایل index.js مربوط به import ، host app زیر را انجام میدهیم: 
import('./startup');
در فایل remote app ، startup.js ‌ها را همانند زیر import می‌کنیم و سپس آن‌ها را در فایل index.html میزبان سوار می‌کنیم.
import { mount as remote1Mount } from 'remote1/RemoteApp1';
import { mount as remote2Mount } from 'remote2/RemoteApp2';

remote1Mount(document.querySelector('#remote1-app'));
remote2Mount(document.querySelector('#remote2-app'));

در ادامه جهت مشاهده خروجی، app میزبان را با دستور npm start اجرا می‌کنیم. اکنون شما می‌توانید خروجی remote app ‌ها را در host app  ببینید. لازم به ذکر است که در هنگام اجرای دستور npm start  برای host app ، هر دو remote app  ایجاد شده باید در حالت اجرا باشند. 



ملاحظات

Module federation در Webpack  نسخه 5 به بالا، در دسترس قرار دارد. شما می‌توانید وابستگی‌های بین remote app و host app را به اشتراک بگذارید. این کار را با اضافه کردن آن‌ها به تنظیمات پلاگین module federation برای remote app و host app  انجام دهید.
new ModuleFederationPlugin({
  name: 'remote1',
  filename: 'remoteEntry.js',
  exposes: {
    './RemoteApp1': './src/startup',
  },
  shared:['react', 'react-dom']
}),

ارتباط بین host و remote app می‌تواند از طریق callback‌ها انجام شود. 
 

مطالب
کدامیک از بسته‌های NET Core. را باید دریافت کنیم؟
زمانیکه به صفحه‌ی دریافت نگارش‌های مختلف NET Core. مراجعه می‌کنیم، بسته‌های مختلفی از یک نگارش قابل مشاهده هستند و در بدو امر واضح نیست که کدامیک را باید دریافت کرد. در این مطلب تفاوت‌های بین این بسته‌ها را مرور خواهیم کرد.


کدام نگارش‌های NET Core. بر روی سیستم شما نصب هستند؟

پیش از انجام هرکاری نیاز است بررسی کنیم کدامیک از بسته‌های ارائه شده، بر روی سیستم جاری نصب هستند. برای انجام اینکار دستور زیر را در خط فرمان صادر کنید:
 dotnet --info
اگر این دستور کار نکرد و خطایی را دریافت کردید، یعنی NET Core. اصلا بر روی سیستم شما نصب نیست. برنامه dotnet.exe جزئی از runtime نصب شده‌است و به صورت خودکار به path سیستم اضافه می‌شود. به همین جهت است که در صورت نصب آن، فرمان dotnet در هر مسیری قابل اجرا است.
Runtime تنها ویژگی‌های اساسی جهت اجرای برنامه‌های از پیش کامپایل شده‌ی NET Core. را با اجرای فرمانی مانند dotnet mydll.dll و یا اجرای دستور dotnet --info برای دریافت اطلاعاتی از جزئیات این ویژگی‌ها، به همراه دارد. اما برای کار با سورس کدها، build، publish و هر کار دیگری با آن‌ها، حتما باید SDK نیز نصب شود.

خروجی فرمان فوق بر روی سیستم من چنین چیزی است:
 C:\Users\Vahid>dotnet --info
.NET Core SDK (reflecting any global.json):
 Version: 2.1.301
 Commit: 59524873d6

Runtime Environment:
 OS Name:   Windows
 OS Version:  10.0.17134
 OS Platform: Windows
 RID: win10-x64
 Base Path: C:\Program Files\dotnet\sdk\2.1.301\

Host (useful for support):
  Version: 2.1.1
  Commit:  6985b9f684

.NET Core SDKs installed:
  2.1.300 [C:\Program Files\dotnet\sdk]
  2.1.301 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.NETCore.App 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download
اولین شماره نگارش نمایش داده شده‌ی در این لیست (2.1.301)، شماره نگارش SDK فعال است. سپس شماره نگارش 2.1.1 به معنای شماره نگارش Runtime فعال بر روی سیستم است که هاست dotnet.exe به شمار می‌رود. سپس لیست SDKها و Runtimeهای نصب شده‌ی بر روی سیستم را نمایش می‌دهد.
باید دقت داشت که بر روی یک سیستم می‌توان چندین SDK و چندین Runtime مختلف را نصب کرد و هر پروژه از شماره نگارش خاصی استفاده کند. شماره نگارش runtime استفاده شده‌ی در پروژه‌ها در فایل csproj، توسط مدخل زیر مشخص می‌شود:
 <TargetFramework>netcoreapp2.1</TargetFramework>
در مورد SDK اینطور نیست و همواره از آخرین SDK نصب شده (همان شماره نگارش SDK فعال فوق) استفاده می‌شود، مگر اینکه فایل ویژه‌ای به نام global.json را در ریشه‌ی اصلی solution قرار دهید؛ با این محتوای فرضی:
 {
  "sdk": {
        "version": "2.1.300-rc.31211"
  }
}
در این حالت پروژه‌ی جاری را وادار می‌کنید بجای استفاده‌ی از آخرین SDK نصب شده‌ی بر روی سیستم، از نگارش SDK خاصی استفاده کند.
البته در اکثر موارد نیازی به انجام این کار نیست؛ چون SDK، با تمام نگارش‌های قبلی سازگار است و همواره استفاده‌ی از آخرین SDK نصب شده توصیه می‌شود. به همین جهت فایل global.json را پس از ایجاد یک solution جدید مشاهده نمی‌کنید؛ مگر اینکه خودتان به دلایل خاصی آن‌را اضافه و مقید نمائید.


تفاوت بسته‌های مختلف قابل دریافت NET Core. در چیست؟

زمانیکه برای دریافت آخرین نگارش NET Core. به سایت آن مراجعه می‌کنیم، به ازای هر نگارش، یک چنین لیستی قابل مشاهده است:
• .NET Core Runtime
• .NET Core SDK
• .NET Core Hosting Bundle
• Visual Studio
• ASP.NET Core Installer
و اکنون سؤالی که مطرح می‌شود این است: کدامیک را باید دریافت کرد؟

Visual Studio

اگر کاربر ویندوز هستید، با نصب آخرین نگارش Visual Studio، می‌توانید به همراه آن، آخرین نگارش SDK ،runtime و اجزای هاست برنامه‌های ASP.NET Core بر روی IIS را نیز بر روی سیستم خود نصب کنید.


NET Core SDK.

هدف از ارائه‌ی بسته‌ی SDK، انجام فرآیندهای build‌، اجرا و مدیریت امور مرتبط با NET Core.، بدون استفاده از Visual Studio و بر روی تمام سیستم عامل‌های پشتیبانی شده‌است. زمانیکه یک بسته‌ی SDK را نصب می‌کنید، به همراه آن این موارد نیز نصب می‌شوند:
• .NET Core SDK 
• .NET Core Runtime 
• ASP.NET Core Runtime
به همین جهت حجم آن از بسته‌ی تکی runtime بیشتر است و با نصب آن دیگر نیازی به دریافت مجزای بسته‌های runtime نیست.

بنابراین دلیل نصب آن می‌تواند شامل یکی از موارد زیر باشد:
 - بر روی سیستمی که در حال توسعه‌ی برنامه‌های مبتنی بر NET Core. هستید. این تمام چیزی است که به آن نیاز دارید.
 - بر روی سروری که نیاز است دستور dotnet را برای انجام فرآیندهای build/publish اجرا کند.


NET Core Runtime.

بسته‌های Runtimes، کوچکترین بسته‌ی ممکن در این لیست هستند و هدف از آن‌ها صرفا اجرای برنامه‌های کامپایل شده‌ی NET Core. در سکوهای کاری مختلف پشتیبانی شده‌ی توسط آن است.
باید دقت داشت که اگر برنامه‌ی شما از «ASP.NET Core meta package» استفاده می‌کند، این بسته در runtime لحاظ نشده‌است و در یک چنین حالتی باید بسته‌ی ASP.NET Core را به صورت جداگانه دریافت و نصب کنید. هرچند اگر از این متاپکیج‌ها استفاده نکنید و بسته‌های مورد نیاز را به صورت مستقیم به برنامه‌ی خود اضافه کنید، این بسته‌ها جزئی از فایل‌های publish نهایی بوده و در این حالت برنامه توسط بسته‌ی runtime نیز قابل اجرا است.
در این حالت برنامه‌ی dotnet بجز اجرای برنامه‌ها و ارائه‌ی اطلاعاتی در مورد خود آن، کارهای دیگری را مانند build و یا publish، نمی‌تواند انجام دهد و برنامه در این حالت باید کاملا از پیش کامپایل شده باشد.

بنابراین دلیل نصب آن می‌تواند شامل یکی از موارد زیر باشد:
- برای اجرای برنامه‌های از پیش کامپایل شده‌ای که به همراه تمام وابستگی‌های مورد نیاز هم هستند.
- برای اجرای برنامه‌های وبی که از ASP.NET Meta packages استفاده نمی‌کنند


ASP.NET Core Installer

همانطور که در توضیحات بسته‌ی runtime عنوان شد، این بسته، متاپکیج‌های ASP.NET Core را به همراه ندارد. اگر به آن‌ها نیاز دارید، باید آن‌ها را به صورت جداگانه توسط ASP.NET Core installer نصب کنید که شامل این موارد است:
- The ASP.NET Runtime Meta Packages
- Microsoft.AspNetCore.App
- Microsoft.AspNetCore.All
 
NET Core Windows Hosting Pack.

نصب این بسته برای هاست برنامه‌های ASP.NET Core در ویندوز و بر روی IIS ضروری است و شامل این اجزا می‌شود:
- 32 bit and 64 .NET Core Runtimes
- ASP.NET Runtime Packages (Microsoft.AspNetCode.App/All)
- IIS Hosting Components
بنابراین این بسته شامل تمام موارد یاد شده‌است منهای قابلیت‌های SDK برای build و publish برنامه‌ها.



بنابراین به صورت خلاصه

برای سرورها این موارد را نصب کنید:
- در ویندوز: Windows Server Hosting Bundle
- برای Mac و لینوکس:  .NET Core Runtime + ASP.NET Core Runtimes

برای سیستم توسعه‌ی شخصی این موارد را نصب کنید:
- SDK
- اگر از ویندوز استفاده می‌کنید: Visual Studio هم به همراه SDK نصب می‌شود.

برای اجرای برنامه‌های از پیش کامپایل شده که به همراه تمام وابستگی‌های مورد نیاز هم هستند:
- تنها Runtime را نصب کنید.
اگر این برنامه‌ی از پیش کامپایل شده از ASP.NET Runtime Meta packages استفاده می‌کند:
- ASP.NET Runtimes را نیز نصب کنید.
اشتراک‌ها
Prism Scaffolder برای تنبل ها

بعد از نصب پکیج در کنسول nuget این فرمان را وارد کنید:

Scaffold Add-ViewAndViewModel Person -Force

با این فرمان کلاس PersonViewModel.cs و PersonViewModel.xaml متناظر با آن تولید می‌شود. سپس به کمک Designer ویژوال استودیو و یا دستی، کدهای تولید شده را مطابق نیاز خود تغییر دهید.

Prism Scaffolder برای تنبل ها
مطالب
Roslyn #4
بررسی API کامپایل Roslyn

Compilation API، یک abstraction سطح بالا از فعالیت‌های کامپایل Roslyn است. برای مثال در اینجا می‌توان یک اسمبلی را از Syntax tree موجود، تولید کرد و یا جایگزین‌هایی را برای APIهای قدیمی CodeDOM و Reflection Emit ارائه داد. به علاوه این API امکان دسترسی به گزارشات خطاهای کامپایل را میسر می‌کند؛ به همراه دسترسی به اطلاعات Semantic analysis. در مورد تفاوت Syntax tree و Semantics در قسمت قبل بیشتر بحث شد.
با ارائه‌ی Roslyn، اینبار کامپایلرهای خط فرمان تولید شده مانند csc.exe، صرفا یک پوسته بر فراز Compilation API آن هستند. بنابراین دیگر نیازی به فراخوانی Process.Start بر روی فایل اجرایی csc.exe مانند یک سری کتابخانه‌های قدیمی نیست. در اینجا با کدنویسی، به تمام اجزاء و تنظیمات کامپایلر، دسترسی وجود دارد.


کامپایل پویای کد توسط Roslyn

برای کار با API کامپایل، سورس کد، به صورت یک رشته در اختیار کامپایلر قرار می‌گیرد؛ به همراه تنظیمات ارجاعاتی به اسمبلی‌هایی که نیاز دارد. سپس کار کامپایلر شروع خواهد شد و شامل مواردی است مانند تبدیل متن دریافتی به Syntax tree و همچنین تبدیل مواردی که اصطلاحا به آن‌ها Syntax sugars گفته می‌شود مانند خواص get و set دار به معادل‌های اصلی آن‌ها. در اینجا کار Semantic analysis هم انجام می‌شود و شامل تشخیص حوزه‌ی دید متغیرها، تشخیص overloadها و بررسی نوع‌های بکار رفته‌است. در نهایت کار تولید فایل باینری اسمبلی، از اطلاعات آنالیز شده صورت می‌گیرد. البته خروجی کامپایلر می‌تواند اسمبلی‌های exe یا dll، فایل XML مستندات اسمبلی و یا فایل‌های .netmudule و .winmdobj مخصوص WinRT هم باشد.
در ادامه، اولین مثال کار با Compilation API را مشاهده می‌کنید. پیشنیاز اجرای آن همان مواردی هستند که در قسمت قبل بحث شدند. یک برنامه‌ی کنسول ساده‌ی .NET 4.6 را آغاز کرده و سپس بسته‌ی نیوگت Microsoft.CodeAnalysis را در آن نصب کنید. در ادامه کدهای ذیل را به پروژه‌ی آماده شده اضافه کنید:
static void firstCompilation()
{
    var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }");
 
    var mscorlibReference = MetadataReference.CreateFromFile(typeof (object).Assembly.Location);
 
    var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
 
    var comp = CSharpCompilation.Create("Demo")
                                .AddSyntaxTrees(tree)
                                .AddReferences(mscorlibReference)
                                .WithOptions(compilationOptions);
 
    var res = comp.Emit("Demo.dll");
 
    if (!res.Success)
    {
        foreach (var diagnostic in res.Diagnostics)
        {
            Console.WriteLine(diagnostic.GetMessage());
        }
    }
}
در اینجا نحوه‌ی کامپایل پویای یک قطعه کد متنی سی‌شارپ را به DLL معادل آن مشاهده می‌کنید. مرحله‌ی اول اینکار، تولید Syntax tree از رشته‌ی متنی دریافتی است. سپس متد CSharpCompilation.Create یک وهله از Compilation API مخصوص #C را آغاز می‌کند. این API به صورت Fluent طراحی شده‌است و می‌توان سایر قسمت‌های آن‌را به همراه یک دات پس از ذکر متد، به طول زنجیره‌ی فراخوانی، اضافه کرد. برای نمونه در این مثال، نحوه‌ی افزودن ارجاعی را به اسمبلی mscorlib که System.Object در آن قرار دارد و همچنین ذکر نوع خروجی DLL یا DynamicallyLinkedLibrary را ملاحظه می‌کنید. اگر این تنظیم ذکر نشود، خروجی پیش فرض از نوع .exe خواهد بود و اگر mscorlib را اضافه نکنیم، نوع int سورس کد ورودی، شناسایی نشده و برنامه کامپایل نمی‌شود.
متدهای تعریف شده توسط Compilation API به یک s جمع، ختم می‌شوند؛ به این معنا که در اینجا در صورت نیاز، چندین Syntax tree یا ارجاع را می‌توان تعریف کرد.
پس از وهله سازی Compilation API و تنظیم آن، اکنون با فراخوانی متد Emit، کار تولید فایل اسمبلی نهایی صورت می‌گیرد. در اینجا اگر خطایی وجود داشته باشد، استثنایی را دریافت نخواهید کرد. بلکه باید خاصیت Success نتیجه‌ی آن‌را بررسی کرده و درصورت موفقیت آمیز نبودن عملیات، خطاهای دریافتی را از مجموعه‌ی Diagnostics آن دریافت کرد. کلاس Diagnostic، شامل اطلاعاتی مانند محل سطر و ستون وقوع مشکل و یا پیام متناظر با آن است.


معرفی مقدمات Semantic analysis

Compilation API به اطلاعات Semantics نیز دسترسی دارد. برای مثال آیا Type A قابل تبدیل به Type B هست یا اصلا نیازی به تبدیل ندارد و به صورت مستقیم قابل انتساب هستند؟ برای درک بهتر این مفهوم نیاز است یک مثال را بررسی کنیم:
        static void semanticQuestions()
        {
            var tree = CSharpSyntaxTree.ParseText(@"
using System;
 
class Foo
{
    public static explicit operator DateTime(Foo f)
    {
        throw new NotImplementedException();
    }
 
    void Bar(int x)
    {
    }
}");
 
            var mscorlib = MetadataReference.CreateFromFile(typeof (object).Assembly.Location);
            var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
            var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib).WithOptions(options);
            // var res = comp.Emit("Demo.dll");
 
            // boxing
            var conv1 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Int32),
                comp.GetSpecialType(SpecialType.System_Object)
                );
 
            // unboxing
            var conv2 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Object),
                comp.GetSpecialType(SpecialType.System_Int32)
                );
 
            // explicit reference conversion
            var conv3 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Object),
                comp.GetTypeByMetadataName("Foo")
                );
 
            // explicit user-supplied conversion
            var conv4 = comp.ClassifyConversion(
                comp.GetTypeByMetadataName("Foo"),
                comp.GetSpecialType(SpecialType.System_DateTime)
                );
        }
تا سطر CSharpCompilation.Create این مثال، مانند قبل است و تا اینجا به Compilation API دسترسی پیدا کرده‌ایم. پس از آن می‌خواهیم یک Semantic analysis مقدماتی را انجام دهیم. برای این منظور می‌توان از متد ClassifyConversion استفاده کرد. این متد یک نوع مبداء و یک نوع مقصد را دریافت می‌کند و بر اساس اطلاعاتی که از Compilation API بدست می‌آورد، می‌تواند مشخص کند که برای مثال آیا نوع کلاس Foo قابل تبدیل به DateTime هست یا خیر و اگر هست چه نوع تبدیلی را نیاز دارد؟


برای مثال نتیجه‌ی بررسی آخرین تبدیل انجام شده در تصویر فوق مشخص است. با توجه به تعریف public static explicit operator DateTime در سورس کد مورد آنالیز، این تبدیل explicit بوده و همچنین user defined. به علاوه متدی هم که این تبدیل را انجام می‌دهد، مشخص کرده‌است.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
یک نکته‌ی تکمیلی
فایل global.json هرچند از پروژه‌های VS 2017 حذف شده‌است اما هنوز هم مورد استفاده‌ی CLI آن هست. برای مثال دستورات خط فرمان dotnet new و یا dotnet build آن از فایل global.json قرار گرفته‌ی در پوشه‌ی جاری (جستجوی یافتن آن تا ریشه‌ی درایو جاری هم ادامه خواهد یافت)، جهت تعیین شماره نگارش SDK مورد استفاده، کمک می‌گیرند. برای نمونه اگر نگارش 2 را نصب کرده باشید، دستور dotnet new از آخرین نگارش نصب شده استفاده خواهد کرد. اما اگر می‌خواهید آن‌را کنترل کنید (امکان کار با چندین SDK به صورت همزمان در پروژه‌های مختلف)، نیاز است global.json را به پروژه اضافه کنید.