اشتراک‌ها
قرارگرفتن NET Framework 4.8.1. بر روی Windows Update

.NET Framework 4.8.1 will be available for the following versions of Windows and distribution channels:

  • Windows Update: Windows 11 21H2, Windows 10 21H2 (LTSC), and Windows 10 22H2
  • Microsoft Update Catalog: Windows 11 21H2, Windows 10 21H2 (LTSC), Windows 10 22H2 and Windows Server 2022 (Desktop, Azure Editions), Azure Stack 21H2 and Azure Stack 22H2. 
قرارگرفتن NET Framework 4.8.1. بر روی Windows Update
اشتراک‌ها
چرا نباید برنامه‌های NET. را بر روی ویندوزهای قدیمی اجرا کرد؟

.NET applications however rely on the ciphers provided by the OS, and the only way to get new ciphers into the OS is through a patch from Microsoft. Unsupported versions of Windows typically do not receive these patches, so over time you can expect an increasing number of websites to stop working with .NET applications. 

چرا نباید برنامه‌های NET. را بر روی ویندوزهای قدیمی اجرا کرد؟
مطالب
کار با Visual Studio در ASP.NET Core
پیش نویس: این مقاله ترجمه شده فصل 6 کتاب Pro Asp.Net Core MVC2 می‌باشد.

کار با Visual Studio

در این مقاله، یکسری توضیحاتی در مورد ویژگی‌های کلیدی ویژوال استودیو به برنامه نویس‌های (توسعه دهنده‌های) پروژه‌های Asp.net Core MVC ارائه می‌دهیم.

 
ایجاد یک پروژه

در ابتدا یک پروژه‌ی وب جدید Asp.net core را به نام Working و بر اساس قالب Empty ایجاد می‌کنیم. سپس در کلاس startup، قابلیت MVC را فعال میکنیم (کدهای این قسمت، در فصل 5 کامل شرح داده شده‌است)
 

namespace Working
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}

ایجاد مدل:

یک پوشه جدید را به نام Models ایجاد می‌کنیم و بعد در این پوشه یک کلاس جدید را به نام Product ایجاد می‌کنیم و کدهای زیر را در کلاس ایجاد شده قرار میدهیم (این قسمت در فصل 5 نیز شرح داده شده‌است):
namespace Working.Models
{
    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}
برای ایجاد یک فروشگاه ساده از اشیاء محصول، من یک فایل کلاس را به نام SimpleRepository.cs به پوشه Models اضافه و از آن برای تعریف کلاس استفاده کردم.
 

namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
کلاس Stores، اشیا مدل را در حافظه ذخیره می‌کند. یعنی هر تغییری را که در Model داده باشید، زمانیکه نرم افزار متوقف یا Restart شود، از بین می‌رود. یک فروشگاه ناپیوسته برای مثال در این فصل کافی است. اما این رویکردی نیست که در بسیاری از پروژه‌های واقعی استفاده شود. در فصل 8 یک مثال را پیاده سازی می‌کنیم تا اطلاعات مدل Store را به صورت مداوم در بانک اطلاعاتی ذخیره کند.

نکته: من یک مشخصه (Property) استاتیک را به نام SharedRepository تعریف کردم که دسترسی به SimpleRepository را فراهم می‌کند و می‌تواند در طول برنامه از آن استفاده شود. این بهترین کار نیست، ولی میخواهم یک مشکل رایج را که در توسعه MVC روبرو میشوید، نشان دهم. من راه بهتری را برای کار با اجزای مشترک، در فصل 18 توضیح می‌دهم.


ایجاد Controller و View

در پوشه Controllers، یک فایل جدید را به نام HomeController.cs ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);
    }
}
این یکی Action Method ایی به نام Index است که اطلاعات مدل را دریافت می‌کند و برای View پیشفرض، جهت نمایش ارسال می‌کند. برای ایجاد View هم بر روی پوشه Views/Home راست کلیک کرده و یک View را به نام index.cshtml ایجاد کنید؛ با کدهای زیر:
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <table>
    <thead>
        <tr>
        <td>Name</td>
        <td>Price</td>
        </tr>
        </thead>
    <tbody>            @foreach (var p in Model)
    {<tr>
        <td>@p.Name</td>
        <td>@p.Price</td>
        </tr>}
        </tbody>
    </table>
</body>
</html>
این View شامل یک جدول است که از حلقه foreach Razor، برای ایجاد ردیف‌هایی برای هر شیء مدل استفاده می‌کند. جایی که هر ردیف، حاوی سلول‌هایی برای خواص نام و قیمت است. اگر شما برنامه کاربردی را اجرا کنید، نتایج حاصل را در شکل خواهید دید:

مدیریت بسته‌های نرم افزاری

دو نوع مختلف از بسته‌های نرم افزاری مورد نیاز برای Asp.Net Core MVC وجود دارند.

معرفی NuGet 

ویژوال استودیو به همراه یک ابزار گرافیکی برای مدیریت بسته‌های NET. است که در یک پروژه گنجانده شده‌است. برای باز کردن این ابزار، گزینه Management NuGet Packages for Solution را از منوی Tools ➤ NuGet Package Manager انتخاب کنید. به این ترتیب ابزار NuGet باز می‌شود و لیستی از بسته‌هایی که قبلا نصب شده‌اند، نمایش داده می‌شود؛ همانطور که در شکل زیر نشان داده شده‌است:
 


برگه‌ی Installed، خلاصه‌ای از بسته‌هایی را که قبلا در پروژه نصب شده‌اند، نشان می‌دهد. از برگه‌ی Browse، برای یافتن و نصب بسته‌های جدید می‌توان استفاده کرد و برگه‌ی Updates، فهرست package هایی را که نسخه‌های اخیر آن‌ها منتشر شده‌اند، نمایش می‌دهد.

 
معرفی بسته‌ی MICROSOFT.ASPNETCORE.ALL

اگر شما از نسخه‌های قبلی Asp.Net Core استفاده کرده باشید، باید یک لیست طولانی از بسته‌های NuGet را به پروژه جدید خود اضافه نمایید. Asp.Net Core2 یک بسته‌ی متفاوت را به نام Microsoft.AspNetCore.All معرفی می‌کند.

پکیچ Microsoft.AspNetCore.All یک meta-package است که شامل تمام بسته‌های Nuget مورد نیاز Asp.net Core و MVC Framework است. یعنی شما دیگر نیازی به نصب تک به تک این نوع بسته‌ها ندارید و هنگامیکه برنامه خود را منتشر می‌کنید، هر بسته‌ای از بسته‌های Meta-package که مورداستفاده قرار نمی‌گیرند، حذف خواهند شد. البته این بسته در نگارش 2.1، قسمت All آن به App تغییر نام یافته‌است.
 
معرفی بسته‌های Nuget و موقعیت ذخیره سازی آن‌ها

ابزار NuGet لیست بسته‌های پروژه را در فایل projectname.csproj نگهداری می‌کند. در اینجا <projectname> با نام پروژه جایگزین میشود. برای مثال در پروژه فوق اطلاعات Nuget، در فایل WorkingWithVisualStudio.csproj ذخیره می‌شوند. ویژوال استودیو محتویات فایل csproj را در پنجره‌ی Solution Explorer نمایش نمی‌دهد. برای ویرایش این فایل، روی پروژه در پنجره‌ی Solution Explorer راست کلیک کنید و گزینه‌ی Edit WorkWithVisualStudio.csproj را از منوی باز شده، انتخاب کنید. ویژوال استودیو فایل را برای ویرایش باز می‌کند. فایل csproj یک فایل XML است و شما در آن عنصری را مانند قطعه کد زیر در آن می‌بینید که Asp.net Core Meta package را به پروژه اضافه می‌کند:
<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
در اینجا یک بسته با نام و شماره نسخه مورد نیاز مشخص شده‌است. اگرچه بسته Meta-Package شامل تمام ویژگی‌های مورنیاز Asp.Net Core MVC می‌باشد، اما شما هنوز هم باید بسته‌های دیگری را به پروژه اضافه کنید تا بتوانید از ویژگی‌های اضافی خاص آن‌ها استفاده کنید. این بسته‌ها را می‌توان توسط رابط‌های خط فرمان و یا ابزار گرافیکی آن اضافه کرد. حتی شما می‌توانید فایل Csproj را به صورت مستقیم ویرایش کنید و ویژوال استودیو میتواند تغییرات بسته‌ها را شناسایی کرده، دانلود و نصب کند.
 


هنگامیکه از NuGet برای اضافه کردن یک بسته به پروژه‌ی خود استفاده می‌کنید، به صورت خودکار به همراه هر بسته‌ای که به آن وابستگی دارد، نصب می‌شود. شما می‌توانید بسته‌های Nuget و وابستگی‌های آن‌ها را در SolutionExpolrer از طریق گزینه‌ی Dependencies -> Nuget مشاهده کنید که هر یک از بسته‌های موجود در فایل csproj و وابستگی‌های آن‌ها را نشان می‌دهد. برای نمونه بسته Meta-Package ASP.Net Core دارای تعداد زیادی وابستگی است؛ برخی از آنها در شکل زیر دیده میشوند:


 
معرفی Bower

یک بسته Client-Side، شامل محتوایی است که به مشتری ارسال می‌شود؛ مانند فایل‌های جاوا اسکریپت، Css Stylesheets و یا تصاویر. از Nuget برای مدیریت این نوع فایل‌ها در پروژه نیز استفاده میشود. اما اکنون Asp.Net Core MVC پشتیبانی توکاری را از یک ابزار مدیریت بسته‌های سمت کاربر، به نام Bower نیز ارائه می‌دهد. Bower یک ابزار منبع باز ( Open Source ) است که در خارج از مایکروسافت و دنیای NET. توسعه داده شده و نگهداری می‌شود.

نکته: Bower به تازگی منسوخ شده اعلام گردیده‌است. ممکن است هشدارهایی را که ابزارهای جایگزین را پیشنهاد می‌کنند نیز مشاهده کنید. با این حال پشتیبانی از Bower با ویژوال استودیو یکپارچه شده‌است و در نگارش 2.1 ابزار مدیریت سمت کلاینت جدید دیگری را نیز بجای آن معرفی کرده‌اند.
 

معرفی لیست بسته‌های Bower

بسته‌های Bower از طریق فایل ویژه‌ی bower.json مشخص می‌شوند. برای ایجاد این فایل در پنجره Solution Explorer روی پروژه WorkingWithVisualStudio راست کلیک کنید و Add -> New Item را از منوی باز شده انتخاب کنید. سپس قالب مورد نظر Bower Configuration File را از Asp.net Core -> Web -> General  Category انتخاب نمائید؛ مانند تصویر زیر:
 


ویژوال استودیو نام bower.json را برای آن قرار میدهد. برروی ok کلیک می‌کنیم و یک فایل جدید، با محتویات پیشفرض زیر به پروژه اضافه میشود:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {}
}
به علاوه برای فایل Bower.json، تصویر زیر بسته‌های Client Side وابسته به Bower را نشان میدهد. از این قسمت برای اضافه کردن وابستگی‌های برنامه نیز استفاده میشود.


نکته: منبع بسته‌های Bower در لینک http://bower.io/search وجود دارد. شما می‌توانید بسته‌ها مورنظر را در اینجا جستجو و به پروژه اضافه کنید.

بعد از اینکه بسته‌ها نصب شدند، محتویات فایل bower.json به صورت زیر می‌باشد:
{
  "name": "asp.net",
  "private": true,
  "resolutions": {
    "jquery": "3.3.1"
  }
}

در ادامه بسته Bootstrap CSS به پروژه اضافه شده‌است. زمانیکه شما فایل Bower.json را ویرایش می‌کنید، ویژوال استادیو لیستی از نام بسته‌ها و نسخه‌های بسته‌های موجود را نمایش می‌دهد؛ مانند تصویر زیر:


در زمان نوشتن این مطلب، آخرین نسخه‌ی پایدار بسته بوت استرپ، 3،3،7 است. البته اگر در دقت کنید، در اینجا سه گزینه‌ی ارائه شده‌ی توسط ویژوال استودیو وجود دارند: 3.3.7 و 3.3.7^ و 3.3.7~. شماره نسخه می‌تواند در طیف وسیعی از روش‌های مختلف در فایل bower.json مشخص شود. مفیدترین آنها در جدول زیر شرح داده شده‌اند. استفاده از شماره نسخه صریح یک بسته، امن‌ترین راه برای مشخص کردن یک بسته است. این تضمین می‌کند که شما همیشه با همان نسخه کار می‌کنید؛ مگر اینکه عمدا فایل bower.json را برای پاسخ گویی به درخواست‌های دیگری به روز رسانی کنید:
  فرمت    توضیحات 
  3.3.7  بیان شماره مستقیم بسته نصب شده و تطبیق دقیق آن با شمار نسخه ، e.g ، 3.3.7 
  *  با استفاده از یک ستاره به Bower اجازه نصب آخرین نسخه را می‌دهد
3.3.7 =<3.3.7<
پیشوند یک شماره نسخه با < یا =< به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای که بزرگتر یا بزرگتر مساوی آن نسخه‌ی معین است، نصب شود 
3.3.7 =>3.3.7
پیشوند یک شماره نسخه با > یا => به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای را که کوچکتر یا کوچکتر و مساوی نسخه‌ی معین است، نصب شود 
  3.3.7~  پیشوند یک شماره نسخه با یک tilde (با کاراکتر ~ ) به نسخه‌هایی که دو شماره اولیه آن‌ها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره آخر آن نسخه متفاوت باشد. مانند نسخه‌های 3.3.9 و 3.3.8 و اجازه نصب نسخه 3.4.0 را نمیدهد؛ چون شماره دوم آن متفاوت است.
  3.3.7^  پیشوند یک شماره نسخه با یک قلم (کاراکتر ^) به نسخه‌هایی که شماره اول آنها مشابه باشند، اجازه نصب می‌دهد؛ حتی اگر شماره دوم آن‌ها متفاوت باشد. مانند نسخه‌های 3.3.1 و 3.4.1 و 3.5.1 اما نسخه 4.0.0 اجازه نصب ندارد 
 
نکته: برای مثال در این کتاب، من فایل bower.json را مستقیما ایجاد و ویرایش می‌کنم. ویرایش این فایل ساده است و به شما کمک می‌کند تا اطمینان حاصل کنید که نتایج مورد انتظار را در صورت پیگیری به همراه داشته باشد. همچنین ویژوال استودیو ابزار گرافیکی را نیز برای مدیریت بسته‌های bower فراهم می‌کند. شما می‌توانید با کلیک راست بر روی فایل bower.json و انتخاب Manage Bower packages به منوی باز شده دسترسی داشته باشید. ویژوال استادیو فایلهای bower.json را برای تغییرات نظارت می‌کند و به صورت خودکار از ابزار Bower برای دانلود و نصب بسته‌ها استفاده می‌کند. هنگامیکه شما تغییرات فایل را ذخیره می‌کنید، ویژوال استودیو بسته‌ی BootStrap را دانلود می‌کند و در پوشه‌ی wwwroot/lib ذخیره می‌کند.


مانند Nuget نیز Bower وابستگی‌های مرتبط با بسته‌های اضافه شده‌ی به یک پروژه را مدیریت می‌کند. BootStrap برای دسترسی به برخی از ویژگی‌های پیشرفته، به JQuery که یک کتابخانه‌ی جاوا اسکریپتی است، تکیه می‌کند. به همین دلیل است که دو بسته را در شکل فوق نشان داده است. شما می‌توانید لیست بسته‌ها و وابستگی‌های آنها را به صورت باز شده در بخش مورد نظر در Solution Explorer مشاهده کنید.

به روزرسانی بسته Bootstrap

در ادامه کتاب، من از نسخه قبلی Bootstrap CSS framework استفاده می‌کنم. هنگامی که دارم این را می‌نویسم، تیم Bootstrap در حال توسعه‌ی نسخه‌ی 4 bootStrap است و چندین بار منتشر شده‌است. این نسخه‌ها به عنوان "آلفا" برچسب گذاری شده‌اند، اما کیفیت آن‌ها بالا است و برای استفاده در نمونه‌های این کتاب به اندازه کافی پایدار است. با توجه به انتخاب نوشتن این کتاب با استفاده از Bootstrap 3 و نسخه پیش از نسخه بوت استرپ 4 و به زودی بایگانی شدن آن، تصمیم گرفتم از نسخه جدید استفاده کنم؛ حتی اگر برخی از نام‌های کلاس‌ها که برای شیوه نامه‌های عناصر HTML استفاده می‌شوند، احتمالا قبل از انتشار نهایی تغییر یابند. این مورد به این معنا است که شما باید همان نسخه از Bootstrap را که برای گرفتن نتایج موردنظر از خروجی نیاز دارید، استفاده  کنید.

برای به روزرسانی بسته Bootstrap، شماره نسخه را در فایل bower.json تغییر دهید. مانند کد زیر:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "4.0.0-alpha.6"
  }
}
زمانی که شما تغییرات فایل bower.json را ذخیره می‌کنید، ویژوال استودیو نسخه جدید BootStrap را دانلود می‌کند.
معرفی توسعه و کامپایل مداوم

توسعه نرم افزار وب اغلب می‌تواند یک فرآیند تکراری باشد، جایی که تغییرات کوچکی را به ویووها یا کلاس‌ها می‌دهید و برنامه را اجرا می‌کنید تا اثرات آن را آزمایش کنید. MVC و ویژوال استودیو همکاری می‌کنند تا از این رویکرد مداوم استفاده کنند تا تغییرات را سریع‌تر و آسان‌تر ببینید.

 اعمال تغییرات در Razor Views  
در زمان توسعه، تغییراتی که به Razor View اعمال میشوند، به محض رسیدن درخواست‌های HTTP، از مرورگر دریافت میشوند. برای اینکه ببینید چطور کار می‌کند، برنامه را با انتخاب گزینه Start Debugging از منوی Debug اجرا کنید و هنگامیکه یک برگه‌ی مرورگر باز شد و اطلاعات نمایش داده شد، تغییراتی را که در زیر نمایش میدهم در فایل Index.cshtml اعمال کنید.

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
            <tr>
                <td>@p.Name</td>
                <td>@($"{p.Price:C2}")</td>
            </tr>}
        </tbody>
    </table>
</body>
</html>
تغییرات را در فایل Index ذخیره می‌کنیم و صفحه وب را با استفاده از دکمه browser Reload مجددا بارگذاری می‌کنیم. تغییرات در View (یک عنوان و فرمت را برای مشخصه Price به عنوان ارز وارد کردیم) در مرورگر هم اعمال شده است؛ مانند تصویر زیر:



اعمال تغییرات در کلاس‌های #C

برای کلاس‌های #C، از جمله کنترلرها و مدل‌ها، دو رویکرد موجود را که از طریق آیتم‌های مختلف در منوی Debug انتخاب می‌شوند، شرح می‌دهم:

Start Without Debugging
تغییرات در کلاس‌ها در پروژه به صورت خودکار زمانیکه یک درخواست HTTP دریافت می‌شود، برای مشاهده‌ی یک تجربه‌ی توسعه‌ی پویا، کامپایل می‌شوند. در این حالت برنامه بدون امکانات دیباگ و اشکال‌زادیی اجرا می‌شود.

Start Debugging
به شما اجزا میدهد صریح تغییرات را کامپایل کنید و برنامه را اجرا کنید ، بررسی مشکلات هم در زمان اجرا پروژه انجام میگیرد.به شما اجرا بررسی و تجزیه و تحلیل هر گونه مشکل در کد را میدهد.

 
کامپایل خودکار کلاس ها

در طول توسعه عادی، این چرخه کامپایل سریع به شما اجازه می‌دهد تا فورا تاثیر تغییرات خود را ببینید؛ حالا می‌تواند این تغییر اضافه نمودن یک اکشن جدید و یا ویرایش نمایش اطلاعات یک Model باشد. برای ارائه‌ی این نوع از توسعه، ویژوال استودیو به محض رسیدن درخواست HTTP از مرورگر، تغییرات را دریافت و کلاس‌ها را به صورت خودکار کامپایل می‌کند. برای دیدن اینکه چگونه کار می‌کند، گزینه Start Without Debugging را از منوی Debug در ویژوال استودیو انتخاب کنید. هنگامیکه مرورگر داده‌های برنامه را نمایش می‌دهد، تغییرات زیر را در فایل Home controller ایجاد کنید:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products
            .Where(p => p.Price < 50));
    }
}
در این تغییرات با استفاده از LINQ محصولات را فیلتر می‌کنیم به طوری که فقط کالاهایی را که price آنها کمتر از 50 است، نمایش داده می‌شوند. تغییرات را در فایل کلاس controller ذخیره کنید و پنجره مرورگر را دوباره باز کنید. بدون توقف و یا راه اندازی مجدد برنامه در ویژوال استادیو، درخواست HTTP از مرورگر باعث عملیات کامپایل میشود و برنامه با استفاده از تغییرات کلاس Controller، دوباره راه اندازی خواهد شد و نتیجه را در زیر میتوانید ببینیدکه محصولات Kayak را از جدول حذف می‌کند.

ویژگی کامپایل خودکار زمانی مفید است که همه چیز برنامه ریزی شود. مشکل این است که خطاهای کامپایلر، در زمان اجرا و در مرورگر بجای ویژوال استودیو نمایش داده می‌شوند. در این حالت زمانیکه یک مشکل وجود دارد، سخت می‌توان متوجه شد که چه مشکلی ایجاد شده است. برای مثال، کدهای زیر اضافه کردن یک مقدار Null را به مجموعه نمایش میدهد. 
namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
            products.Add("Error", null);
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
مشکلی مانند ورودی Null تا زمانیکه برنامه اجرا نشود، نمایش داده نمیشود. بارگذاری صفحه مرورگر باعث می‌شود کلاس SimpleRepository به صورت خودکار کامپایل شود و برنامه دوباره راه اندازی خواهد شد. هنگامیکه MVC نمونه‌ای از کلاس Controller را برای پردازش درخواست HTTP از مرورگر ایجاد می‌کند، سازنده HomeController کلاس SimpleRepository را ایجاد خواهد کرد که به نوبه خود سعی می‌کند مقدار Null اضافه شده در لیست را پردازش کند. مقدار Null باعث بروز یک مشکل می‌شود، اما مشخص نیست مشکل چیست. مرورگر یک پیام مفید را نمایش نمی‌دهد.
توانایی نمایش صفحات خطاها  
زمانیکه مشکلی در پنجره‌ی مرورگر ایجاد شد، می‌توان یک راهنمای با اطلاعات مفید را نمایش داد. این قابلیت را می‌توانید با فعال کردن نمایش صفحات انجام داد که باید در تنظیمات کلاس Startup تغییرات زیر را اعمال کنید.

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDeveloperExceptionPage();
            }
        }
    }
}
اگر پنجره مرورگر را دوباره بارگذاری کنید، فرآیند کامپایل خودکار به صورت خودکار برنامه را بازسازی می‌کند و یک پیام خطای مفید‌تری را در مرورگر ایجاد می‌کند. مانند تصویر زیر:

پیام خطایی که توسط مرورگر نشان داده می‌شود، می‌تواند برای کشف مشکلات ساده، کافی باشد. اما برای مشکلات پیچیده‌تر و برای مشکلاتی که بلافاصله آشکار نمی‌شوند، ویژوال استودیو برای پیگیری خطا مورد نیاز است.

 
استفاده از Debugger

ویژوال استادیو از اجرای یک برنامه MVC با استفاده از Debugger نیز پشتیبانی می‌کند که اجازه می‌دهد برنامه برای بررسی وضعیت نرم افزار و دنبال کردن درخواستی که به برنامه ارسال میشود، متوقف و از این طریق، پیگیری شود. این مورد نیاز به یک سبک متفاوت از توسعه را دارد. زیرا تغییراتی را در کلاس‌های #C میدهیم، تا زمانیکه برنامه مجددا راه اندازی نشود، اعمال نمی‌شوند ( هرچند تغییرات Razor View هنوز هم به صورت خودکار اعمال میشوند). این سبک توسعه به همراه استفاده‌ی از ویژگی کامپایل خودکار نیست؛ اما Debugger ویژوال استودیو عالی است و می‌تواند بینش عمیق‌تری را در مورد نحوه‌ی کارکرد برنامه داشته باشد. برای اجرای برنامه با استفاده Debugger، در ویژوال استودیو از منوی Debug گزینه‌ی Start Debugging را انتخاب کنید. ویژوال استودیو کلاسهای #C در پروژه را قبل از اجرای برنامه کامپایل می‌کند. اما شما همچنان می‌توانید با استفاده از موارد موجود در منوی Build، کد خود را به صورت دستی نیز کامپایل کنید.

مثال فوق حاوی مقدار NULL است که سبب می‌شود یک NullReferenceException توسط کلاس SimpleRepository پرتاب شود. این حالت برنامه را قطع و کنترل اجرا را به توسعه دهنده منتقل می‌کند؛ همانطور که در شکل زیر نشان داده شده است



نکته: اگر Debugger خطا را نفهمد، گزینه‌ی Windows ➤ exception settings را از منوی Debugger ویژوال استودیو انتخاب کنید و اطمینان حاصل کنید که تمام انواع خطاهای در لیست خطاهای زمان اجرای زبان مشترک، تایید شده‌است.
تنظیم یک Break-point

Debugger عامل اصلی خطا را نمایش نمی‌دهد؛ تنها مکان آن‌را آشکار می‌کند. عبارتیکه ویژوال استودیو برجسته می‌کند نشان می‌دهد که این مشکل زمانی رخ می‌دهد که فیلتر کردن اشیاء با استفاده از LINQ انجام شود، اما یک کار کوچک لازم است تا از جزئیات کاسته شود و به علت اصلی برسد.
Breakpoint عبارتی است که به Debugger می‌گوید تا برنامه را متوقف کند و کنترل دستی برنامه را به برنامه نویس میدهد. شما می‌توانید وضعیت برنامه را بازبینی کنید و ببینید چه اتفاقی می‌افتد و به صورت اختیاری روند کاری را دوباره ادامه دهید.
برای ایجاد Breakpoint، روی عبارت راست کلیک کنید و در منوی باز شده، گزینه Breakpoint -> Insert Breakpoint را انتخاب کنید.

به عنوان مثال: یک Breakpoint به خط کد AddProduct در کلاس SimpleRepository اعمال کنید. همانطور که در شکل زیر نمایش داده میشود:
 


برنامه را اجرا کنید؛ با استفاده از Debug -> Start Debugging و یا با استفاده از Debug -> Restart برنامه را Restart می‌کنیم. در طی درخواست اولیه HTTP، برنامه اجرا میشود تا به نقطه‌ای که Break Point دارد برسد و در آنجا برنامه متوقف میشود. در این نقطه، شما می‌توانید از آیتم‌های منوی Debug ویژوال استودیو یا کنترل‌ها در بالای پنجره، برای کنترل اجرای برنامه استفاده کنید؛ یا از نمایش‌های مختلف Debugger موجود از طریق Debug -> Windows برای بررسی وضعیت برنامه استفاده می‌کنیم.
مشاهده مقادیر داده در ویرایشگر کد
رایج‌ترین استفاده Break Point، ردیابی مشکلات در کد شماست. قبل از اینکه بتوانید یک مشکل را رفع کنید، باید بدانید چه اتفاقی در حال رخ دادن است و یکی از ویژگیهای مفید ویژوال استودیو این است که توانایی مشاهده و کنترل ارزش متغیرها را درست در ویرایشگر کد، میدهد.
اگر اشاره‌گر ماوس را بر روی پارامتر p به متد AddProduct که توسط Debugger برجسته شده‌است، حرکت دهید، یک فرم ظاهر خواهد شد که ارزش فعلی p را نشان می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است. من یک نمونه بزرگ شده از محتویات فرم ظاهر شده را نمایش میدهم تا به راحتی بتوانید متن در آن را بخوانید.
 


این مورد ممکن است مؤثر به نظر نرسد، چون شیء داده در یک سازنده همانند BreakPoint تعریف شده‌است. اما این ویژگی‌ها برای هر متغیری کار می‌کند. شما می‌توانید مقادیر را مشاهده کنید تا مقادیر خود و فیلد آنها را ببینید. هر مقدار دارای یک دکمه پین​​ کوچک به سمت راست است. برای زمانیکه کد در حال اجراست، برای نظارت بر مقدار، از آن استفاده کنید.
اشاره‌گر ماوس را بر روی متغیر P قرار دهید و مرجع محصول را پین کنید. مرجع پیوست شده را باز کنید تا بتوانید نام و قیمت را نیز ببینید؛ مانند شکل زیر:
 


گزینه Continue را از منوی Debug در ویژوال استادیو انتخاب کنید تا برنامه ادامه پیدا کند. از آنجا که در برنامه حلقه Foreach وجود دارد، برنامه که دوباره اجرا میشود، وقتی مجددا به BreakPoint رسید، برنامه متوقف میشود. مقادیر پین شده در شکل زیر نشان میدهند که چگونه متغیر P و خواص آن تغییر می‌کنند.
 


استفاده از پنجره متغیرهای محلی ( Local Windows )

یکی از ویژگی‌های مرتبط، پنجره Locals است که با انتخاب گزینه‌ی منوی Debug ➤ Windows ➤ Locals باز می‌شود. پنجره‌ی Locals، مقدار متغیرها را به شکلی مشابه پنل پین شده نمایش می‌دهد، اما در اینجا تمام اشیاء محلی را نسبت به Break Point نمایش می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است:
 


هربار که Continue را انتخاب می‌کنید، اجرای برنامه ادامه یافته و یک شیء دیگر توسط حلقه foreach پردازش می‌شود.
اگر ادامه دهید، در زمان ویرایش کد، در هر دو پنجره Locals و در مقادیر پنل پین شده، شما مرجع Null را می‌بینید. برای کنترل اجرای برنامه، می‌توانید جریان را از طریق کد خود در دیباگر دنبال کنید و احساس کنید که چه اتفاقی می‌افتد.

برای غیرفعال کردن BreakPoint، روی  عبارت راست کلیک کنید و از منوی باز شده گزینه Delete BreakPoint را انتخاب کنید. برنامه را دوباره راه اندازی کنید و جدول داده ساده‌ای را که در شکل نشان داده شده، مشاهده خواهید کرد.


 
استفاده از Browser Link

ویژگی Browser Link می‌تواند روند توسعه را با قرار دادن یک یا چند مرورگر تحت کنترل ویژوال استودیو، ساده سازی کند. این ویژگی مخصوصا مفید است اگر شما نیاز به دیدن اثر تغییرات را در طیف وسیعی از مرورگرها دارید. قابلیت Browser Link با و یا بدون Debugger کار می‌کند و به این معنا است که می‌توانیم هر فایلی را در پروژه تغییر دهیم و تاثیر تغییر را بدون نیاز به تغییری در مرورگر مشاهده کنیم.

 
راه اندازی BrowserLink

برای فعال کردن Browser Link باید در کلاس Startup، تنظیمات را تغییر دهید. مانند کد زیر:

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


استفاده از Browser Link

برای درک اینکه Browser Link چگونه کار می‌کند، در ویژوال استودیو گزینه Start Without Debugging را از منوی Debug انتخاب می‌کنیم. ویژوال استودیو برنامه را اجرا می‌کند و یک برگه جدید مرورگر را برای نمایش نتیجه باز می‌کند. با بازبینی HTML ارسال شده به مرورگر، شما خواهید دید که حاوی بخش دیگری مانند این است:
 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            <tr><td>Lifejacket</td><td>&#xA3;48.95</td></tr>
            <tr><td>Soccer ball</td><td>&#xA3;19.50</td></tr>
            <tr><td>Corner flag</td><td>&#xA3;34.95</td></tr>
        </tbody>
    </table>
    <!-- Visual Studio Browser Link -->
    <script type="application/json" id="__browserLink_initializationData">
        {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false}
    </script>
    <script type="text/javascript" src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink" async="async"></script> <!-- End Browser Link -->
</body>
</html>
نکته: اگر قسمت اضافی را نمی‌بینید، لینک مرورگر را از منوی نشان داده شده‌ی در شکل زیر فعال کنید و مرورگر را دوباره بارگذاری کنید.


ویژوال استادیو یک جفت عناصر اسکریپت را به HTML فرستاده شده‌ی به مرورگر اضافه می‌کند که برای بازکردن یک اتصال طولانی مدت HTTP با سرور برنامه کاربردی است؛ تا زمانیکه ویژوال استودیو مجددا برنامه را ری‌استارت کند. کد زیر تغییر در فایل Index و تاثیر استفاده از Browser Link را نشان میدهد.
 

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
تغییر در فایل View را ذخیره کنید و Refresh Linked Browsers را از منوی Browser Link در نوار ابزار ویژوال استودیو انتخاب کنید؛ همانطور که در شکل نشان داده شده است.  اگر Browser Link کار نمی‌کند، بارگیری مجدد مرورگر یا راه اندازی مجدد ویژوال استادیو را امتحان کنید).
 


کد جاوا اسکریپتی که در HTML ارسال شده به مرورگر جاسازی شده، صحفه را دوباره بارگذاری می‌کند؛ برای دیدن تاثیرات کد اضافه شده که اضافه کردن  یک timestamp ساده است.
 
نکته: عناصر اسکریپت Browser Link فقط در پاسخ‌های موفق جاسازی شده است. به این معنا که اگر یک خطا هنگام کامپایل در هنگام اجرا کردن یک Razor View یا مدیریت یک درخواست ایجاد شود، اتصال بین مرورگر و ویژوال استودیو از بین میرود و شما بعد از حل مشکل باید صفحه را مجدد بارگذاری کنید.

 
استفاده از مرورگرهای متعدد

Browser Link می‌تواند برای نمایش یک برنامه در مرورگرهای متعددی به طور همزمان استفاده شود و می‌تواند زمانی مفید باشد که شما می‌خواهید تفاوت‌های پیاده سازی را بین مرورگرهای مختلف کنترل کنید و یا ببینید که چگونه یک برنامه بر روی ترکیبی از مرورگرهای دسکتاپ و تلفن همراه ارائه می‌شود.
برای انتخاب مرورگرهایی که استفاده می‌شوند، مرورگر را با استفاده از دکمه IIS Express در نوار ابزار ویژوال استودیو، انتخاب کنید؛ همانطور که در شکل زیر نشان داده شده است.
 


ویژوال استودیو لیستی از مرورگرهایی را که در مورد آنها اطلاعاتی دارد، نمایش میدهد. در عکس زیر مرورگرهایی را که من در سیستم خود نصب کرده‌ام، نشان می‌دهد. برخی از آنها با ویندوز مانند Internet Explorer و Edge نصب می‌شوند.

 
ویژوال استادیو معمولا مرورگرهای رایجی را که نصب میشوند، نمایش میدهد. اما شما می‌توانید با استفاده از دکمه‌ی Add، برای اضافه کردن مرورگری که به صورت خودکار لیست نشده نیز استفاده کنید. همچنین می‌توانید ابزار تست شخص ثالث مانند Browser Stack را نیز راه اندازی کنید که مرورگرها را بر روی سرویس‌های ابری میزبان ( cloud-hosted ) و ماشین‌های مجازی اجرا می‌کند.

من سه مرورگر را در شکل انتخاب کردم: Chrome ، Internet Explorer و Edge. با کلیک بر روی دکمه Browse، فعالیت هر سه مرورگر شروع می‌شود و باعث می‌شود URL مثال برنامه را بارگذاری کند؛ همانطور که در شکل نشان داده شده است.
 


با استفاده از منوی Browser Link Dashboard، شما می‌توانید ببینید که چه مرورگرهایی در Browser Link انتخاب شده‌اند. داشبورد آن نشانی اینترنتی نمایش داده شده توسط هر مرورگر را نشان می‌دهد و در اینجا هر مرورگر را می‌توان به صورت جداگانه رفرش کرد.
 


آماده سازی جاوا اسکریپت و CSS برای استقرار

هنگامی که Client-Side بخشی از یک برنامه وب را ایجاد می‌کنید، معمولا تعدادی از فایل‌های جاوا اسکریپت و CSS سفارشی را تهیه می‌کنید که برای تکمیل آن‌ها، از بسته‌های نصب شده‌ی توسط Bower استفاده می‌شود. این فایل‌ها نیاز به پردازش دارند تا آنها را برای تحویل در یک محیط تولید، بهینه سازی کنند تا تعداد درخواستهای HTTP و میزان پهنای باند شبکه مورد نیاز برای ارسال آنها به مشتری، به حداقل برسد. این فرآیند به عنوان بسته بندی شناخته می‌شود.
 

فعال کردن تحویل محتوای استاتیک

ASP.Net Core شامل پشتیبانی از ارائه فایل‌های استاتیک از پوشه wwwroot به مشتریان است. اما این امکان به صورت پیشفرض در زمان ایجاد یک پروژه‌ی خالی جدید فعال نیست و شما باید با قرار دادن عبارتی در فایل StartUp آن را فعال کنید؛ مانند کد زیر:
 

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseStaticFiles();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


اضافه کردن محتوای استاتیک به پروژه

برای نشان دادن فرآیند بسته بندی، من نیاز به اضافه کردن تعدادی محتوای استاتیک به پروژه و یکی کردن آن‌ها با برنامه‌ی نمونه را دارم. برای این منظور ابتدا یک پوشه‌ی جدید را به نام wwwroot/css ایجاد کنید که محل متداولی برای فایل‌های سفارشی CSS است. من فایلی را به نام First.css با استفاده از قالب آیتم Style Sheet اضافه کردم؛ همانطور که در شکل زیر نشان داده شده است. قالب Style Sheet در مسیر Asp.Net Core -> Web -> Content Section وجود دارد.
 


فایل First.Css را ویرایش کنید و محتوای زیر را در آن قرار دهید.
h3 {
}

table, td {
    border: 2px solid black;
    border-collapse: collapse;
    padding: 5px;
}
من این روند را تکرار کردم و یک فایل دیگر را نیز به نام second.css در پوشه wwwroot/css ایجاد کردم.

فایل‌های جاوا اسکریپت معمولا در پوشه wwwroot/js قرار میگیرند. من این پوشه را ایجاد کردم. فایل‌های جاوا اسکریپت را می‌توانید در مسیر Asp.Net Core -> Web -> Script انتخاب کنید. همانطور که در شکل زیر نشان داده شده است.


من کد جاوا اسکریپتی ساده زیر را به این فایل جدید اضافه کردم؛ همانطور که در لیست نشان داده شده است.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the third.js file";
    document.querySelector("body").appendChild(element);
});

من به بیش از یک فایل جاوا اسکریپت نیاز دارم. بنابراین فایل دیگری را به نام fourth.js نیز در پوشه wwwroot ایجاد می‌کنم و محتوای زیر را در آن قرار میدهم.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the fourth.js file";
    document.querySelector("body").appendChild(element);
});


به روز رسانی View

گام نهایی، به روز رسانی فایل Index.cshtml برای استفاده از Css و فایل جاوا اسکریپت است. کد‌های آن در زیر نشان داده شده است:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/first.css" />
    <link rel="stylesheet" href="css/second.css" />
    <script src="js/third.js"></script>
    <script src="js/fourth.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه کاربردی را اجرا کنید، محتویات نشان داده شده‌ی در شکل زیر را مشاهده خواهید کرد. محتوای موجود توسط شیوه نامه‌های CSS شبیه سازی شده است و کد جاوا اسکریپتی جدیدی را اضافه کرده است.


یکی کردن فایل‌های سمت کلاینت در برنامه‌های MVC

در حال حاضر چهار فایل استاتیک وجود دارند و مرورگر باید چهار درخواست را برای دریافت فایل‌های استاتیک ایجاد کند و هر یک از این فایل‌ها نیازمند پهنای باند بیشتری است که باید به مشتری تحویل داده شود؛ زیرا آنها حاوی فضای سفید و نام متغیرها هستند که برای توسعه دهنده‌ها معنا دار هستند؛ اما برای مرورگرها اهمیتی ندارند.
ترکیب فایل‌هایی هم نوع، تلفیق نامیده می‌شود و در آن کار ساختن فایل‌ها به صورتی کوچکتر انجام می‌شود. هر دوی این کارها در برنامه Asp.Net Core MVC توسط  Bundler & Minifier مخصوص ویژوال استودیو انجام میشود.


نصب افزونه‌های ویژوال استودیو

اولین قدم برای نصب افزونه، انتخاب از منوی Tools -> Extensions and Update و کلیک بر روی مجموعه Online است تا افزونه‌های ویژوال استودیو را در مجموعه نمایش بدهد. نام افزونه را در جعبه جستجوی در گوشه‌ی سمت راست بالای پنجره وارد کنید؛ همانطور که در شکل زیر نشان داده شده است. محل نصب افزونه را مشخص می‌کنیم و بر روی دانلود کلیک می‌کنیم تا آن را به ویژوال استودیو اضافه کند. ویژوال استودیو را مجدد راه اندازی کنید تا فرآیند نصب تکمیل شود.


دسته بندی و یکی کردن فایل‌ها

پس از نصب افزونه، ویژوال استودیو را مجددا راه اندازی کنید و پروژه نمونه را باز کنید. با افزودن افزونه، می‌توانید چندین فایل هم نوع را در Solution Explorer انتخاب کنید. آنها را با یکدیگر ترکیب کرده و محتویات آنها را کوچکتر کنید. به عنوان مثال فایل‌های First.css و Second.css را در Solution Explorer را انتخاب و کلیک راست کرده و سپس Bundler & Minifier -> Bundle and Minify Files را از منوی باز شده انتخاب کنید . همانطور که در شکل زیر نشان داده شده است.
 


فایل خروجی را با عنوان bundle.css ذخیره کنید. در Solution Explorer یک بسته جدید ایجاد میشود. اگر شما این فایل را باز کنید، خواهید دید که محتویات هر دو فایل CSS جداگانه ترکیب شده‌اند و تمام فضای سفید آن‌ها حذف شده‌است. البته شما نمی‌خواهید به طور مستقیم با این فایل کار کنید؛ اما این فایل کوچکتر است و فقط یک اتصال HTTP را برای ارائه CSS styles به مشتری نیاز دارد.

مراحل قبل را برای فایل‌های third.js و fourth.js تکرار کنید تا فایل‌های جدید bundle.js و bundle.min.js در پوشه wwwroot ایجاد شوند.

احتیاط: اطمینان حاصل کنید که فایل‌ها را به ترتیبی که توسط مرورگر بارگیری می‌شوند، انتخاب کنید تا ترتیب دستورات Style‌ها یا دستورات کد را در فایل‌های خروجی حفظ کنید. به عنوان مثال دقت کنید که فایل third.js قبل از فایل fourth.js انتخاب شده باشد تا مطمئن باشید دستورات به ترتیب و به درستی اجرا می‌شوند.
کد زیر، عناصر پیوند فایل‌های جداگانه‌ای را که باید در فایل Index.cshtml قرار گیرند، نمایش میدهد:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/bundle.min.css" />
    <script src="js/bundle.min.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه را اجرا کنید، هیچ تغییر بصری وجود نخواهد داشت؛ اما فایلهای آن یکی شده‌اند و با حجم کمتر و با تعداد اتصالات کمتری از سرور دریافت می‌شوند.

همان زمان که عملیات جمع آوری و یکی کردن را انجام می‌دهید، رکورد عملیات انجام شده را در فایلی به نام bundleconfig.json در پوشه‌ی wwwroot پروژه نگهداری می‌کند. در اینجا یک نمونه از فایل تولیدی را مشاهده می‌کنید:
[
  {
    "outputFileName": "Views/wwwroot/css/bundle.css",
    "inputFiles": [
      "Views/wwwroot/css/First.css",
      "Views/wwwroot/css/second.css"
    ]
  },
  {
    "outputFileName": "Views/wwwroot/js/bundle.js",
    "inputFiles": [
      "Views/wwwroot/js/fourth.js",
      "Views/wwwroot/js/third.js"
    ]
  }
]
 

خلاصه
در این بخش من توضیحاتی را در مورد ویژگی‌هایی که ویژوال استودیو برای طراحی برنامه‌های وب به توسعه دهنده‌ها ارائه میدهد، شرح دادم که شامل کامپایل خودکار کلاس‌ها، Browser Link و یکی کردن فایل‌های سمت کلاینت ( bundling and minification ) بود. 
مطالب
غنی سازی کامپایلر C# 9.0 با افزونه‌ها
از زمانیکه کامپایلر #C، تحت عنوان Roslyn بازنویسی شد، قابلیت افزونه‌پذیری نیز پیدا کرد. برای مثال می‌توان آنالیز کننده‌ای را طراحی کرد که در پروسه‌ی کامپایل متداول کدهای  #C مورد استفاده قرار گرفته و خطاها و یا اخطارهایی را صادر کند که جزئی از پیام‌های استاندارد کامپایلر #C نیستند. در این مطلب نحوه‌ی معرفی آن‌ها را به پروژه‌های جدید NET 5.0.، بررسی می‌کنیم.


معرفی تعدادی آنالیز کننده‌ی کد که به عنوان افزونه‌ی کامپایلر #C قابل استفاده هستند

Microsoft.CodeAnalysis.NetAnalyzers
این افزونه جزئی از SDK دات نت 5 است و نیازی به نصب مجزا را ندارد. البته اگر می‌خواهید نگارش‌های جدیدتر آن‌را پیش از یکی شدن با SDKهای بعدی مورد آزمایش قرار دهید، می‌توان آن‌را به صورت صریحی نیز به کامپایلر معرفی کرد. این افزونه‌ی جایگزین FxCop است و پس از ارائه‌ی آن، FxCop را منسوخ شده اعلام کردند.

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

Microsoft.VisualStudio.Threading.Analyzers
این افزونه نکاتی را در مورد مشکلات Threading موجود در کدها، گوشزد می‌کند.

Microsoft.CodeAnalysis.BannedApiAnalyzers
با استفاده از این افزونه می‌توان استفاده‌ی از یکسری کدها را ممنوع کرد. برای مثال استفاده‌ی از System.DateTimeOffset.DateTime، در سراسر کدها ممنوع شده و استفاده‌ی از System.DateTimeOffset.UtcDateTime پیشنهاد شود.

AsyncFixer و Asyncify
این دو افزونه، مشکلات متداول در حین کار با کدهای async را گوشزد می‌کنند.

ClrHeapAllocationAnalyzer
این افزونه مکان‌هایی از کد را مشخص می‌کنند که در آن‌ها تخصیص حافظه صورت گرفته‌است. کاهش این مکان‌ها می‌تواند به بالا رفتن کارآیی برنامه کمک کنند.

SonarAnalyzer.CSharp
مجموعه‌ی معروف Sonar، که تعداد قابل ملاحظه‌ای بررسی کننده‌ی کد را به پروژه‌ی شما اضافه می‌کنند.


روش معرفی سراسری افزونه‌های فوق به تمام پروژه‌های یک Solution

می‌توان تنظیمات زیر را به یک تک پروژه اعمال کرد که برای اینکار نیاز است فایل csproj آن‌را ویرایش نمود و یا می‌توان یک تک فایل ویژه را به نام Directory.Build.props ایجاد کرد و آن‌را به صورت زیر تکمیل نمود. محل قرارگیری این فایل، در ریشه‌ی Solution و در کنار فایل sln می‌باشد.
<Project>
  <PropertyGroup>
    <AnalysisLevel>latest</AnalysisLevel>
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
    <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <Nullable>enable</Nullable>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
    <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
    <!--
      CA2007: Consider calling ConfigureAwait on the awaited task
      MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
      CA1056: Change the type of property 'Url' from 'string' to 'System.Uri'
      CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object
      CA1055: Change the return type of method from 'string' to 'System.Uri'
    -->
    <NoWarn>$(NoWarn);CA2007;CA1056;CA1054;CA1055;MA0004</NoWarn>
    <NoError>$(NoError);CA2007;CA1056;CA1054;CA1055;MA0004</NoError>
    <Deterministic>true</Deterministic>
    <Features>strict</Features>
    <ReportAnalyzer>true</ReportAnalyzer>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Meziantou.Analyzer" Version="1.0.639">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.8.55">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="AsyncFixer" Version="1.3.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Asyncify" Version="0.9.7">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="ClrHeapAllocationAnalyzer" Version="3.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="SonarAnalyzer.CSharp" Version="8.16.0.25740">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt" />
  </ItemGroup>
</Project>
توضیحات:
- در تنظیمات فوق، مواردی مانند AnalysisLevel، در مطلب «کامپایلر C# 9.0، خطاها و اخطارهای بیشتری را نمایش می‌دهد» پیشتر بررسی شده‌اند.
- در اینجا Nullable به true تنظیم شده‌است. اگر قرار است یک پروژه‌ی جدید را شروع کنید، بهتر است این ویژگی را نیز فعال کنید. بسیاری از API‌های دات نت 5 جهت مشخص سازی خروجی نال و یا غیرنال آن‌ها، بازنویسی و تکمیل شده‌اند و بدون استفاده از این ویژگی، بسیاری از راهنمایی‌های ارزنده‌ی دات نت 5 را از دست خواهید داد. اساسا بدون فعالسازی این ویژگی، از قابلیت‌های #C مدرن استفاده نمی‌کنید.
- وجود این PackageReference ها، به معنای بالا رفتن حجم نهایی قابل ارائه‌ی پروژه نیست؛ چون به صورت PrivateAssets و analyzers تعریف شده‌اند و فقط در حین پروسه‌ی کامپایل، جهت ارائه‌ی راهنمایی‌های بیشتر، تاثیرگذار خواهند بود.
- این تنظیمات طوری چیده شده‌اند که تا حد ممکن «درد آور» باشند! برای اینکار CodeAnalysisTreatWarningsAsErrors و TreatWarningsAsErrors به true تظیم شده‌اند تا حتی اخطارها نیز به صورت خطای کامپایلر گزارش شوند؛ تا مجبور به رفع آن‌ها شویم.
- در اینجا فایل BannedSymbols.txt را نیز مشاهده می‌کنید که مرتبط است به BannedApiAnalyzers. می‌توان در کنار فایل Directory.Build.props، فایل جدید BannedSymbols.txt را با این محتوا ایجاد کرد:
# https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md
P:System.DateTime.Now;Use System.DateTime.UtcNow instead
P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead
P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead
در این حالت برای مثال، از استفاده‌ی از DateTime.Now منع شده و وادار به استفاده‌ی از DateTime.UtcNow می‌شوید.


روش کاهش تعداد خطاهای نمایش داده شده

اگر از فایل Directory.Build.props فوق استفاده کرده و یکبار دستور dotnet restore را جهت بازیابی وابستگی‌های آن اجرا کنید، با تعداد خطاهایی که در IDE خود مشاهده خواهید کرد، شگفت‌زده خواهید شد! به همین جهت برای کنترل آن‌ها می‌توان فایل جدید editorconfig. را به نحو زیر در کنار فایل Directory.Build.props ایجاد و تکمیل کرد:
[*.cs]

# MA0026 : Complete the task
dotnet_diagnostic.MA0026.severity = suggestion

# CA1308: In method 'urlToLower', replace the call to 'ToLowerInvariant' with 'ToUpperInvariant' (CA1308)
dotnet_diagnostic.CA1308.severity = suggestion

# CA1040: Avoid empty interfaces
dotnet_diagnostic.CA1040.severity = suggestion

# CA1829 Use the "Count" property instead of Enumerable.Count()
dotnet_diagnostic.CA1829.severity = suggestion

# Use 'Count' property here instead.
dotnet_diagnostic.S2971.severity = suggestion

# S1135 : Complete the task
dotnet_diagnostic.S1135.severity = suggestion

# S2479: Replace the control character at position 7 by its escape sequence
dotnet_diagnostic.S2479.severity = suggestion

# CA2007: Consider calling ConfigureAwait on the awaited task
dotnet_diagnostic.CA2007.severity = none

# MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
dotnet_diagnostic.MA0004.severity = none

# CA1056: Change the type of property 'Url' from 'string' to 'System.Uri'
dotnet_diagnostic.CA1056.severity = suggestion

# CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object
dotnet_diagnostic.CA1054.severity = suggestion

# CA1055: Change the return type of method from 'string' to 'System.Uri'
dotnet_diagnostic.CA1055.severity = suggestion

# S4457: Split this method into two, one handling parameters check and the other handling the asynchronous code.
dotnet_diagnostic.S4457.severity = none

# AsyncFixer01: Unnecessary async/await usage
dotnet_diagnostic.AsyncFixer01.severity = suggestion

# AsyncFixer02: Long-running or blocking operations inside an async method
dotnet_diagnostic.AsyncFixer02.severity = error

# VSTHRD103: Call async methods when in an async method
dotnet_diagnostic.VSTHRD103.severity = error

# AsyncFixer03: Fire & forget async void methods
dotnet_diagnostic.AsyncFixer03.severity = error

# VSTHRD100: Avoid async void methods
dotnet_diagnostic.VSTHRD100.severity = error

# VSTHRD101: Avoid unsupported async delegates
dotnet_diagnostic.VSTHRD101.severity = error

# VSTHRD107: Await Task within using expression
dotnet_diagnostic.VSTHRD107.severity = error

# AsyncFixer04: Fire & forget async call inside a using block
dotnet_diagnostic.AsyncFixer04.severity = error

# VSTHRD110: Observe result of async calls
dotnet_diagnostic.VSTHRD110.severity = error

# VSTHRD002: Avoid problematic synchronous waits
dotnet_diagnostic.VSTHRD002.severity = suggestion

# MA0045: Do not use blocking call (make method async)
dotnet_diagnostic.MA0045.severity = suggestion

# AsyncifyInvocation: Use Task Async
dotnet_diagnostic.AsyncifyInvocation.severity = error

# AsyncifyVariable: Use Task Async
dotnet_diagnostic.AsyncifyVariable.severity = error

# VSTHRD111: Use ConfigureAwait(bool)
dotnet_diagnostic.VSTHRD111.severity = none

# MA0022: Return Task.FromResult instead of returning null
dotnet_diagnostic.MA0022.severity = error

# VSTHRD114: Avoid returning a null Task
dotnet_diagnostic.VSTHRD114.severity = error

# VSTHRD200: Use "Async" suffix for async methods
dotnet_diagnostic.VSTHRD200.severity = suggestion

# MA0040: Specify a cancellation token
dotnet_diagnostic.MA0032.severity = suggestion

# MA0040: Flow the cancellation token when available
dotnet_diagnostic.MA0040.severity = suggestion

# MA0079: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.MA0079.severity = suggestion

# MA0080: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.MA0080.severity = error

#AsyncFixer05: Downcasting from a nested task to an outer task.
dotnet_diagnostic.AsyncFixer05.severity = error

# ClrHeapAllocationAnalyzer ----------------------------------------------------
# HAA0301: Closure Allocation Source
dotnet_diagnostic.HAA0301.severity = suggestion

# HAA0601: Value type to reference type conversion causing boxing allocation
dotnet_diagnostic.HAA0601.severity = suggestion

# HAA0302: Display class allocation to capture closure
dotnet_diagnostic.HAA0302.severity = suggestion

# HAA0101: Array allocation for params parameter
dotnet_diagnostic.HAA0101.severity = suggestion

# HAA0603: Delegate allocation from a method group
dotnet_diagnostic.HAA0603.severity = suggestion

# HAA0602: Delegate on struct instance caused a boxing allocation
dotnet_diagnostic.HAA0602.severity = suggestion

# HAA0401: Possible allocation of reference type enumerator
dotnet_diagnostic.HAA0401.severity = silent

# HAA0303: Lambda or anonymous method in a generic method allocates a delegate instance
dotnet_diagnostic.HAA0303.severity = silent

# HAA0102: Non-overridden virtual method call on value type
dotnet_diagnostic.HAA0102.severity = silent

# HAA0502: Explicit new reference type allocation
dotnet_diagnostic.HAA0502.severity = none

# HAA0505: Initializer reference type allocation
dotnet_diagnostic.HAA0505.severity = silent
روش کار هم به صورت است که برای مثال در IDE خود (حتی با VSCode هم کار می‌کند)، خطای کامپایلر مثلا CA1308 را مشاهده می‌کنید که عنوان کرده‌است بجای ToLowerInvariant از ToUpperInvariant استفاده کنید. اگر با این پیشنهاد موافق نیستید (عین خطا را به صورت C# CA1308 در گوگل جستجو کنید؛ توضیحات مایکروسافت را در مورد آن خواهید یافت)، یک سطر شروع شده‌ی با dotnet_diagnostic و سپس ID خطا را به صورت زیر، به فایل editorconfig. یاد شده، اضافه کنید:
dotnet_diagnostic.CA1308.severity = suggestion
به این ترتیب هنوز هم این مورد را به صورت یک پیشنهاد مشاهده خواهید کرد، اما دیگر جزو خطاهای کامپایلر گزارش نمی‌شود. اگر خواستید که به طور کامل ندید گرفته شود، مقدار آن‌را بجای suggestion به none تغییر دهید.

یک نکته: در ویندوز نمی‌توانید یک فایل تنها پسوند دار را به صورت معمولی در windows explorer ایجاد کنید. نام این فایل را به صورت .editorconfig. با دو نقطه‌ی ابتدایی و انتهایی وارد کنید. خود ویندوز نقطه‌ی پایانی را حذف می‌کند.


روش صرفنظر کردن از یک خطا، تنها در یک قسمت از کد

فرض کنید نمی‌خواهید خطای CA1052 را تبدیل به یک suggestion سراسری کنید و فقط می‌خواهید که در قطعه‌ی خاصی از کدهای خود، آن‌را خاموش کنید. به همین جهت بجای اضافه کردن آن به فایل editorconfig.، باید از ویژگی SuppressMessage به صورت زیر استفاده نمائید:
[SuppressMessage("Microsoft.Usage", "CA1052:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
[SuppressMessage("Microsoft.Usage", "RCS1102:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
[SuppressMessage("Microsoft.Usage", "S1118:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
public class Program { }
در اینجا پارامتر اول با Microsoft.Usage مقدار دهی می‌شود. پارامتر دوم آن باید حاوی ID خطا باشد. در صورت تمایل می‌توانید دلیل خاموش کردن این خطا را در قسمت Justification وارد کنید.
مطالب
بررسی Source Generators در #C - قسمت چهارم - روش دسترسی به تنظیمات برنامه
در حین توسعه‌ی Source Generators، نیاز می‌شود تا بتوان تنظیماتی را از استفاده کننده دریافت کرد؛ برای مثال تعیین فضای نام ویژه‌ای، فعال و غیرفعال کردن قابلیتی و یا حتی دریافت فایل‌های تکمیلی. این تنظیمات سفارشی از طریق تعریف آن‌ها در فایل‌های csproj. و خواص MSBuild قابل دسترسی هستند که روش کار با آن‌ها را در ادامه مرور خواهیم کرد.


روش تعریف خواص سفارشی MSBuild در پروژه‌ی Source Generator

در مثال همین سری، به پوشه‌ی NotifyPropertyChangedGenerator مراجعه کرده و فایل جدید NotifyPropertyChangedGenerator.props را با محتوای زیر به آن اضافه می‌کنیم:
<Project>
    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_CustomRootNamespace"/>        
    </ItemGroup>
</Project>
هدف این است که خاصیت جدیدی به نام SourceGenerator_CustomRootNamespace، توسط استفاده کننده در فایل csproj. برنامه‌، قابل تعریف شود. علت قرار دادن این تعاریف در یک فایل props. مجزا، آماده کردن این پروژه جهت ارائه‌ی به صورت یک بسته‌ی نیوگت است که نیاز به تنظیمات ذیل را نیز دارد:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
        <IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
    </PropertyGroup>

    <ItemGroup>
        <!-- Package the generator in the analyzer directory of the nuget package -->
        <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>

        <!-- Package the props file -->
        <None Include="NotifyPropertyChangedGenerator.props" Pack="true" PackagePath="build" Visible="true"/>
    </ItemGroup>
</Project>
با این تنظیمات، فایل props. یاد شده، در پوشه‌ی build بسته‌ی نیوگت قرار می‌گیرد و با سیستم build پروژه‌ی استفاده کننده یکی می‌شود. همچنین باید در تنظیمات دیگری، مقدار PackagePath را به analyzers/dotnet/cs و IncludeBuildOutput را به false تنظیم کرد تا تولید کننده‌ی کد، در پوشه‌ی مخصوص آنالایزرها قرار گیرد و نه در جای دیگری. اگر این موارد رعایت نشوند، بسته‌ی نیوگت نهایی، سبب تولید کدی نخواهد شد و کار نمی‌کند.

یک نکته‌ی مهم! اگر بخواهیم مستقیما از پروژه‌ی source generator خود مثلا در پروژه‌ی NotifyPropertyChangedGenerator.Demo این سری همانند قبل استفاده کنیم، تنظیمات ذکر شده‌ی در فایل props. فوق، در آن قابل دسترسی نخواهند بود و با پروسه‌ی build یکی نمی‌شوند. تنظیماتی که تا اینجا ذکر شدند، فقط مخصوص بسته‌ی نیوگت نهایی است. برای استفاده‌ی مستقیم از آن‌ها در پروژه‌ی Demo، نیاز است یکبار دیگر محتویات props. تولید کننده‌ی کد را داخل فایل csproj. پروژه‌ی Demo، تعریف کرد. یا می‌توان از روش استفاده از فایل ویژه‌ی Directory.Build.props و قابلیت‌های ارث‌بری آن استفاده کرد. یعنی یک فایل Directory.Build.props را در بالاترین سطح ممکن قرار داد و CompilerVisiblePropertyها را در آن تعریف کرد تا در تمام پروژه‌های برنامه قابل دسترسی شوند.


روش تعریف خواص سفارشی MSBuild در پروژه‌ی استفاده کننده از Source Generator

در مثال این سری چون از بسته‌ی نیوگت تولید کننده‌ی کد استفاده نمی‌کنیم، نیاز است خاصیت سفارشی تعریف شده را یکبار دیگر داخل فایل csproj. پروژه‌ی Demo تعریف کنیم. پس از آن می‌توان این خاصیت را در قسمت PropertyGroup مقدار دهی کرد:
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <SourceGenerator_CustomRootNamespace>ThisIsTest</SourceGenerator_CustomRootNamespace>
    </PropertyGroup>


    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_CustomRootNamespace"/>
    </ItemGroup>
</Project>
البته بدیهی است اگر از بسته‌ی نیوگت تولید کننده‌ی کد استفاده شود، نیازی به ذکر قسمت CompilerVisibleProperty نخواهد بود و آن‌را به صورت خودکار از فایل props. به همراه بسته‌ی نیوگت دریافت می‌کند.


روش دسترسی به مقدار خاصیت سفارشی MSBuild در پروژه‌ی Source Generator

پس از این مقدمات، خواص عمومی MSBuild از طریق خاصیت AnalyzerConfigOptions.GlobalOptions و متد TryGetValue آن با فرمول زیر قابل دسترسی هستند. قسمت build_property ثابت بوده و جزو موارد توکار MSBuild است:
internal static class SourceGeneratorContextExtensions
{
    public static string GetMSBuildProperty(
        this GeneratorExecutionContext context,
        string property,
        string defaultValue = "")
    {
        return !context.AnalyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{property}", out var value)
            ? defaultValue
            : value;
    }
}
برای نمونه روش دسترسی به خاصیت SourceGenerator_CustomRootNamespace تعریف شده، در متد Execute به صورت زیر است:
[Generator]
public class NotifyPropertyChangedGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var customRootNamespace = context.GetMSBuildProperty("SourceGenerator_CustomRootNamespace", "Test");


معرفی خاصیت ویژه‌ی AdditionalFiles

تا اینجا روش تعریف یک خاصیت جدید MSBuild و روش دسترسی به آن‌را بررسی کردیم. خاصیت توکاری به نام AdditionalFiles نیز در MSBuild تعریف شده‌است که در پروژه‌های Source Generator جهت دسترسی به فایل‌ها و محتوای آن‌ها قابل استفاده است. برای نمونه می‌توان در فایل csproj. پروژه‌ی Demo تعریف زیر را ارائه کرد:
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <AdditionalFiles Include="file1.txt" Visible="false"/> 
    </ItemGroup>
</Project>
که در اینجا file1.txt، مسیر فایلی است که در پروژه‌ی Source Generator از طریق خاصیت context.AdditionalFiles قابل دسترسی است. AdditionalFiles یک آرایه‌است؛ یعنی می‌توان در پروژه‌ی Demo، چندین AdditionalFiles را تعریف و استفاده کرد. هر AdditionalText که معرف اجزای AdditionalFiles است، به همراه مسیر فایل معرفی شده و همچنین متد GetText است.
خاصیت Visible در اینجا مشخص می‌کند که آیا file1.txt در IDE، در کنار لیست سایر فایل‌ها نمایش داده شود یا خیر.


امکان تعریف خواص سفارشی بر روی AdditionalFiles

فرض کنید علاقمندیم خاصیت ویژه‌ای را به AdditionalFiles اضافه کنیم؛ برای مثال به نام SourceGenerator_EnableLogging مانند مثال زیر:
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <AdditionalFiles Include="file2.txt" SourceGenerator_EnableLogging="true" Visible="false"/> 
    </ItemGroup>
</Project>
روش انجام اینکار به صورت زیر است:
الف) فایل NotifyPropertyChangedGenerator.props تعریف شده را به صورت زیر تکمیل می‌کنیم:
<Project>
    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_CustomRootNamespace"/>
        
        <CompilerVisibleProperty Include="SourceGenerator_EnableLogging"/>
        <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceGenerator_EnableLogging"/>
    </ItemGroup>
</Project>
یعنی در اینجا هم می‌توان خاصیت SourceGenerator_EnableLogging را به صورت سراسری در قسمت PropertyGroupهای استفاده کننده تعریف کرد و همچنین توسط CompilerVisibleItemMetadata و MetadataName آن، آن‌‌را به AdditionalFiles نیز انتساب داد. برای مثال اگر AdditionalFiles ای به همراه ویژگی SourceGenerator_EnableLogging نبود، اگر مقدار سراسری استفاده شود.

ب) در فایل csproj. پروژه‌ی Demo، از این خواص و متادیتاها استفاده می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <SourceGenerator_EnableLogging>true</SourceGenerator_EnableLogging>
    </PropertyGroup>

    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_EnableLogging"/>
        <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceGenerator_EnableLogging"/>

        <AdditionalFiles Include="file1.txt" Visible="false"/>  <!-- logging will be controlled by default, or global value -->
        <AdditionalFiles Include="file2.txt" SourceGenerator_EnableLogging="true" Visible="false"/>  <!-- always enable logging for this file -->
        <AdditionalFiles Include="file3.txt" SourceGenerator_EnableLogging="false" Visible="false"/> <!-- never enable logging for this file -->
    </ItemGroup>
</Project>
همانطور که عنوان شد چون از بسته‌ی نیوگت تولید کننده‌ی کد استفاده نمی‌کنیم، نیاز است خواص جدید تعریف شده را یکبار دیگر هم در اینجا تکرار کنیم. پس از آن امکان مقدار دهی SourceGenerator_EnableLogging میسر می‌شود.

ج) برای خواندن این خواص در پروژه‌ی Source generator به صورت زیر عمل می‌شود:
internal static class SourceGeneratorContextExtensions
{
    public static string GetAdditionalFilesOption(
        this GeneratorExecutionContext context,
        AdditionalText additionalText,
        string property,
        string defaultValue = "")
    {
        return !context.AnalyzerConfigOptions.GetOptions(additionalText)
            .TryGetValue($"build_metadata.AdditionalFiles.{property}", out var value)
            ? defaultValue
            : value;
    }
}
- در اینجا build_metadata.AdditionalFiles ثابت بوده و جزو تنظیمات MSBuild است.
- روش تامین AdditionalText این متد را نیز در مثال زیر مشاهده می‌کنید که حاصل ایجاد حلقه‌ای بر روی context.AdditionalFiles‌های دریافتی است:
[Generator]
public class NotifyPropertyChangedGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var customRootNamespace = context.GetMSBuildProperty("SourceGenerator_CustomRootNamespace", "Test");

        var globalLoggingSwitch = context.GetMSBuildProperty("SourceGenerator_EnableLogging", "false");
        var emitLoggingGlobal = globalLoggingSwitch.Equals("true", StringComparison.OrdinalIgnoreCase);

        foreach (var file in context.AdditionalFiles)
        {
            var perFileLoggingSwitch = context.GetAdditionalFilesOption(file, "SourceGenerator_EnableLogging");
            var emitLogging = string.IsNullOrWhiteSpace(perFileLoggingSwitch)
                ? emitLoggingGlobal // allow the user to override the global logging on a per-file basis
                : perFileLoggingSwitch.Equals("true", StringComparison.OrdinalIgnoreCase);

            // add the source with or without logging...
        }
در این مثال یکبار به مقدار سراسری SourceGenerator_EnableLogging ارجاع شده که در قسمت PropertyGroupها تعریف شده و سپس درون حلقه، به متادیتای تعریف شده‌ی بر روی AdditionalFiles اشاره می‌کند.
اشتراک‌ها
بررسی تعاریف نواحی زمانی در 120 سال قبل

A 2016 study found that the economic impact of Daylight Saving , due to increased heart attacks, workplace accidents and cyberloafing (slacking off at work!) cost the US economy $434 million each year, there are a great many others that present similar evidence.

In 2019 the European Union voted to abolish daylight saving in 2021, similarly the “Sunshine Protection Act of 2021” is gaining traction in the US. Over the next decade I feel quite sure that most of the globe will reject daylight saving entirely. 

بررسی تعاریف نواحی زمانی در 120 سال قبل
اشتراک‌ها
SQL Server 2022 قابلیت تهیه گزارش از کوئری‌های بد نوشته شده را دارد
One of the new Extended Event available in SQL Server 2022 is the query_antipattern. This extended event allows to identify anti-patterns on the SQL queries sent to the server.  An anti-pattern in this case is some code that the SQL Server optimizer can’t do a great job optimizing the code (but cannot correct the issue automatically). 
SQL Server 2022 قابلیت تهیه گزارش از کوئری‌های بد نوشته شده را دارد
نظرات مطالب
آشنایی و بررسی ابزار Glimpse
سپاس از مطب شما
زمانی که با entity framework 6 استفاده می‌کنیم
خطا زیر رو میده راه حل برایش مشکل وجود دارد
No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.Odbc'. Make sure the provider is registered in the 'entityFramework' section of the application config file. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.