مطالب
PowerShell 7.x - قسمت ششم - ایجاد Cmdletها توسط #C
تا اینجا با کمک توابع توانستیم PowerShell را به اصطلاح extend کنیم. نوع دیگر دستورات، command letها هستند. این نوع دستورات را با کمک یک زبان دات‌نتی میتوانیم ایجاد کنیم. به این نوع دستورات complied cmdlet گفته میشود. در بیشتر مواقع با کمک advanced functionها میتوانید بیشتر کارها را انجام دهید؛ چراکه به صورت مستقیم امکان استفاده از دات‌نت را درون PowerShell دارید. اما شاید ترجیح دهید از سی‌شارپ یا دیگر زبان‌ها دات‌نتی برای ایجاد یک تابع استفاده کنید.

نحوه‌ی ایجاد یک cmdlet با کمک #C
ابتدا یک دایرکتوری جدید را ایجاد کرده و درون آن یک پروژه‌ی از نوع class library را ایجاد کنید. سپس پکیج PowerShellStandard.Library را درون پروژه ایجاد شده با کمک dotnet cli به پروژه اضافه کنید: 
mkdir ps_cmdlet_with_csharp && cd "$_"
dotnet new classlib
dotnet add package PowerShellStandard.Library
mv Class1.cs GetHelloCommand.cs
در پایان دستورات فوق، نام فایل پیش‌فرض Class1 را نیز به GetHelloCommand تغییر داده‌ایم. در ادامه محتویات فایل را اینگونه ویرایش خواهیم کرد: 
namespace ps_cmdlet_with_csharp;
using System.Management.Automation;

[Cmdlet(VerbsCommon.Get, "Hello")]
public class GetHelloCommand : PSCmdlet
{
    [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
    public string Name { get; set; }
    protected override void BeginProcessing()
    {
        WriteObject("Start processing");
    }
    protected override void ProcessRecord()
    {
        WriteObject("Hello " + Name);
    }
    protected override void EndProcessing()
    {
        WriteObject("End processing");
    }
}
همانطور که مشاهده میکنید، کلاس فوق را از PSCmdlet ارث‌بری کرده‌ایم. در کل برای ایجاد یک command let، از یکی از انواع Cmdlet یا PSCmdlet میتوانیم ارث‌بری کنیم. Cmdlet یک کلاس پایه است که PSCmdlet نیز از آن ارث برده است و یکسری امکانات بیشتری را جهت تعامل با PowerShell ارائه میدهد: 
using System.Collections.ObjectModel;
using System.Management.Automation.Host;

namespace System.Management.Automation
{
    public abstract class PSCmdlet : Cmdlet
    {
        protected PSCmdlet();

        public PSEventManager Events { get; }
        public PSHost Host { get; }
        public CommandInvocationIntrinsics InvokeCommand { get; }
        public ProviderIntrinsics InvokeProvider { get; }
        public JobManager JobManager { get; }
        public JobRepository JobRepository { get; }
        public InvocationInfo MyInvocation { get; }
        public PagingParameters PagingParameters { get; }
        public string ParameterSetName { get; }
        public SessionState SessionState { get; }

        public PathInfo CurrentProviderLocation(string providerId);
        public Collection<string> GetResolvedProviderPathFromPSPath(string path, out ProviderInfo provider);
        public string GetUnresolvedProviderPathFromPSPath(string path);
        public object GetVariableValue(string name);
        public object GetVariableValue(string name, object defaultValue);
    }
}
در نهایت command let باید به یک DLL تبدیل شود؛ چون همانطور که قبلآً نیز اشاره شد، هر command let در واقع یک شیء دات‌نتی است. در ادامه پروژه جاری را بیلد کرده و توسط دستور Import-Module فایل DLL تولید شده را درون Shell ایمپورت خواهیم کرد: 
PS /> dotnet build
PS /> Import-Module ./bin/Debug/net7.0/ps_cmdlet_with_csharp.dll
سپس توسط دستور Get-Command میتوانیم مطمئن شویم که ماژول موردنظر با موفقیت ایمپورت شده‌است: 
PS /> Get-Command -Module ps_cmdlet_with_csharp

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Get-Hello                                          1.0.0.0    ps_cmdlet_with_csharp
همانطور که مشاهده میکنید، درون ماژول ps_cmdlet_with_csharp تنها یک command let تعریف شده‌است. درون یک namespace میتوانیم چندین command let را تعریف کنیم. به عنوان مثال برای مثال قبل میتوانیم: 
namespace ps_cmdlet_with_csharp;
using System.Management.Automation;

[Cmdlet(VerbsCommon.Get, "Hello")]
public class GetHelloCommand : PSCmdlet
{
    // as before
}

[Cmdlet(VerbsCommon.Get, "Greetings")]
public class GetGreetingsCommand : PSCmdlet
{
    [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
    public string Name { get; set; }
    protected override void BeginProcessing()
    {
        WriteObject("Start processing");
    }
    protected override void ProcessRecord()
    {
        WriteObject("Greetings " + Name);
    }
    protected override void EndProcessing()
    {
        WriteObject("End processing");
    }
}
اکنون اگر پروژه را بیلد کنیم و ماژول بیلد شده را ایمپورت کنیم، با خطای زیر مواجه خواهیم شد: 
Import-Module: Invalid assembly public key.
برای رفع این مشکل، فایل csproject را باز کرده و خط زیر را به آن اضافه کنید: 
<Project Sdk="Microsoft.NET.Sdk">

  <!-- other tags -->

  <ItemGroup>
    <Compile Include="./GetHelloCommand.cs" />
  </ItemGroup>

</Project>
اکنون پروژه با موفقیت بیلد و ایمپورت خواهد شد: 
PS /> Get-Command -Module ps_cmdlet_with_csharp

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Get-Greetings                                      1.0.0.0    ps_cmdlet_with_csharp
Cmdlet          Get-Hello                                          1.0.0.0    ps_cmdlet_with_csharp

یک مثال تکمیلی
درون یک کلاس Cmdlet، امکان استفاده از تمامی annotationهایی را که در قسمت قبل بررسی کردیم، اینجا نیز در اختیار داریم؛ بنابراین نیاز به توضیح مجدد آن نیست. در ادامه میخواهیم یک دستور را با عنوان Push-SlackMessage تهیه کنیم که کار ارسال یک پیام را به یک کانال Slack، انجام میدهد: 
namespace ps_cmdlet_with_csharp;
using System.Management.Automation;
using System.Net.Http.Headers;


[Cmdlet(VerbsCommon.Push, "SlackMessage")]
[Alias("ssm")]
[OutputType(typeof(string))]
public class SlackMessageCommand : PSCmdlet
{
    [Parameter(Mandatory = true)]
    [Alias("m")]
    public string Message { get; set; }

    [Parameter(Mandatory = true)]
    [Alias("t")]
    [ValidatePattern(@"xoxp-[0-9]{11}-[0-9]{12}-[0-9]{13}-[0-9a-zA-Z]{32}")]
    public string Token { get; set; }

    [Parameter(Mandatory = true)]
    [Alias("cid")]
    public string ChannelID { get; set; }
    protected async override void ProcessRecord()
    {
        base.ProcessRecord();
        try
        {
            using var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.Token);
            var values = new Dictionary<string, string>
            {
                { "channel", this.ChannelID },
                { "text", this.Message }
            };
            var content = new FormUrlEncodedContent(values);
            var response = await client.PostAsync("https://slack.com/api/chat.postMessage", content);
            var responseString = await response.Content.ReadAsStringAsync();
            WriteObject("Message sent");
        }
        catch (Exception ex)
        {
            WriteObject(ex.Message);
        }
    }
}

یک نکته در مورد ایمپورت کردن ماژول‌ها
دستوراتی که تاکنون ایجاد کردیم (هم توابع و هم compiled cmletها) برای اجرا باید یکبار درون حافظه قرار بگیرند و سپس امکان اجرای آنها را خواهیم داشت. دلیل آن نیز این است که همه چیز درون سشن جاری انجام خواهد شد و به محض بستن آن، تغییرات نیز ار حافظه خارج خواهند شد. یعنی برای command letی که ایجاد کردیم، با هربار باز کردن یک سشن جدید مجبور خواهیم بود که مجدداً آن را ایمپورت کنیم. برای رفع این مشکل میتوانیم از پروفایل‌ها استفاده کنیم. توسط پروفایل، امکان سفارشی‌سازی شل را خواهیم داشت. پروفایل در واقع یک اسکریپت PowerShell است که به محض اجرای PowerShell فراخوانی خواهد شد. بنابراین درون پروفایل این فرصت را خواهیم داشت تا متغیرها، ماژول‌ها، aliaseها و… را قبل از باز کردن شل، درون سشن PowerShell بارگذاری کنیم. PowerShell از چندین نوع پروفایل پشتیبانی میکند و توسط متغیر خودکار PROFILE$ میتوانیم لیست مسیرهای پروفایل‌ها را مشاهده کنیم:  
PS /> $PROFILE | Get-Member -Type NoteProperty | Select-Object Name, Definitionbject Name, Definition

Name                   Definition
----                   ----------
AllUsersAllHosts       string AllUsersAllHosts=/usr/local/microsoft/powershell/7/…
AllUsersCurrentHost    string AllUsersCurrentHost=/usr/local/microsoft/powershell…
CurrentUserAllHosts    string CurrentUserAllHosts=/Users/sirwanafifi/.config/powe…
CurrentUserCurrentHost string CurrentUserCurrentHost=/Users/sirwanafifi/.config/p…
برای ویرایش هر کدام از پروفایل‌های موردنظر میتوانید اینگونه عمل کنید: 
$SlackProjectPath = "/Users/sirwanafifi/Desktop/ps_cmdlet_with_csharp/bin/Debug/net7.0/ps_cmdlet_with_csharp.dll"
Import-Module $SlackProjectPath
اکنون با هر بار باز کردن PowerShell به command let جدید دسترسی خواهید داشت: 
PS /> Get-Command -Module ps_cmdlet_with_csharp

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ----
Cmdlet          Push-SlackMessage                                  1.0.0.0    ps_…
نظرات مطالب
مشکل همزمانی خواندن و به روز رسانی اطلاعات در برنامه‌های وب

در EF Core 8 این امکان وجود داره که دستور ExecuteUpdate و ExecuteDelete رو بدون فراخوانی متد SaveChanges اجرا کنیم و عملیات بروزرسانی را مستقیم در بانک اطلاعاتی انجام بدیم. در مثال مایکروسافت در اجرای این دستورات مقدار version را به متد where داده است. در واقع اجرای متد ضمن بررسی Id باید مقدار version را نیز در نظر بگیرد. برای داشتن مقدار version باید یکبار به بانک اطلاعاتی درخواست ارسال کنم و این مقدار را یکبار از بانک دریافت کنم و بعدا به متد ExecuteUpdate پاس بدم. آیا راهی هست که برای اجرای دستورات مجبور به دریافت مقدار Version از بانک نباشم؟

مطالب
نحوه ارتقاء برنامه‌های موجود MVC3 به MVC4
در ادامه، مراحل ارتقاء پروژه‌های قدیمی MVC3 را به ساختار جدید پروژه‌های MVC4 مرور خواهیم کرد.

1) نصب پیشنیاز
الف) نصب VS 2012
و یا
ب) نصب بسته MVC4 مخصوص VS 2010 (این مورد جهت سرورهای وب نیز توصیه می‌شود)

پس از نصب باید به این نکته دقت داشت که پوشه‌های زیر حاوی اسمبلی‌های جدید MVC4 هستند و نیازی نیست الزاما این موارد را از NuGet دریافت و نصب کرد:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies
C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies
پس از نصب پیشنیازها
2) نیاز است نوع پروژه ارتقاء یابد
به پوشه پروژه MVC3 خود مراجعه کرده و تمام فایل‌های csproj و web.config موجود را با یک ادیتور متنی باز کنید (از خود ویژوال استودیو استفاده نکنید، زیرا نیاز است محتوای فایل‌های پروژه نیز دستی ویرایش شوند).
در فایل‌های csproj (یا همان فایل پروژه؛ که vbproj هم می‌تواند باشد) عبارت
{E53F8FEA-EAE0-44A6-8774-FFD645390401}
را جستجو کرده و با
{E3E379DF-F4C6-4180-9B81-6769533ABE47}
جایگزین کنید. به این ترتیب نوع پروژه به MVC4 تبدیل می‌شود.

3) به روز رسانی شماره نگارش‌های قدیمی
سپس تعاریف اسمبلی‌های قدیمی نگارش سه MVC و نگارش یک Razor را یافته (در تمام فایل‌ها، چه فایل‌های پروژه و چه تنظیمات):
System.Web.Mvc, Version=3.0.0.0
System.Web.WebPages, Version=1.0.0.0
System.Web.Helpers, Version=1.0.0.0
System.Web.WebPages.Razor, Version=1.0.0.0
و این‌ها را با نگارش چهار MVC و نگارش دو Razor جایگزین کنید:
System.Web.Mvc, Version=4.0.0.0
System.Web.WebPages, Version=2.0.0.0
System.Web.Helpers, Version=2.0.0.0
System.Web.WebPages.Razor, Version=2.0.0.0
این کارها را با replace in all open documents توسط notepad plus-plus به سادگی می‌توان انجام داد.

4) به روز رسانی مسیرهای قدیمی
به علاوه اگر در پروژه‌های خود از اسمبلی‌های قدیمی به صورت مستقیم استفاده شده:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies
C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 3\Assemblies
این‌ها را یافته و به نگارش MVC4 و Razor2 تغییر دهید:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies
C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies

5) به روز رسانی قسمت appSettings فایل‌های کانفیگ
در کلیه فایل‌های web.config برنامه، webpages:Version را یافته و شماره نگارش آن‌را از یک به دو تغییر دهید:
<appSettings>
  <add key="webpages:Version" value="2.0.0.0" />
  <add key="PreserveLoginUrl" value="true" />
</appSettings>
همچنین یک سطر جدید PreserveLoginUrl را نیز مطابق تنظیم فوق اضافه نمائید.

6) رسیدگی به وضعیت اسمبلی‌های شرکت‌های ثالث
ممکن است در این زمان از تعدادی کامپوننت و اسمبلی MVC3 تهیه شده توسط شرکت‌های ثالث نیز استفاده نمائید. برای اینکه این اسمبلی‌ها را وادار نمائید تا از نگارش‌های MVC4 و Razor2 استفاده کنند، نیاز است bindingRedirect‌های زیر را به فایل‌های web.config برنامه اضافه کنید (در فایل کانفیگ ریشه پروژه):
<configuration>
  <!--... elements deleted for clarity ...-->
 
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" 
             publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" 
             publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="4.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" 
             publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
اکنون فایل solution را در VS.NET گشوده و یکبار گزینه rebuild را انتخاب کنید تا پروژه مجددا بر اساس اسمبلی‌های جدید معرفی شده ساخته شود.

7) استفاده از NuGet برای به روز رسانی بسته‌های نصب شده
یک سری از بسته‌های تشکیل دهنده MVC3 مانند موارد ذیل نیز به روز شده‌اند که لازم است از طریق NuGet دریافت و جایگزین شوند:
Unobtrusive.Ajax.2
Unobtrusive.Validation.2
Web.Optimization.1.0.0
و ....

برای اینکار در solution explorer روی references کلیک راست کرده و گزینه Manage NuGet Packages را انتخاب کنید. در صفحه باز شده گزینه updates/all را انتخاب کرده و مواردی را که لیست می‌کند به روز نمائید (شامل جی کوئری، EF، structureMap و غیره خواهد بود).


8) اضافه کردن یک فضای نام جدید
بسته Web Optimization را از طریق NuGet دریافت کنید (برای یافتن آن bundling را جستجو کنید؛ نام کامل آن Microsoft ASP.NET Web Optimization Framework 1.0.0 است). این مورد به همراه پوشه MVC4 نیست و باید از طریق NuGet دریافت و نصب شود. (البته پروژه‌های جدید MVC4 شامل این مورد هستند)
در فایل وب کانفیگ، فضای نام System.Web.Optimization را نیز اضافه نمائید:
    <pages>
      <namespaces>
        <add namespace="System.Web.Optimization" />
      </namespaces>
    </pages>

پس از ارتقاء
اولین مشکلی که مشاهده شد:
بعد از rebuild به مقدار پارامتر salt که به نحو زیر در MVC3 تعریف شده بود، ایراد خواهد گرفت:
[ValidateAntiForgeryToken(Salt = "data123")]
Salt را در MVC4 منسوخ شده معرفی کرده‌اند: (^)
علت هم این است که salt را اینبار به نحو صحیحی خودشان در پشت صحنه تولید و اعمال می‌کنند. بنابراین این یک مورد را کلا از کدهای خود حذف کنید که نیازی نیست.


مشکل بعدی:
در EF 5 جای یک سری از کلاس‌ها تغییر کرده. مثلا ویژگی‌های ForeignKey، ComplexType و ... به فضای نام System.ComponentModel.DataAnnotations.Schema منتقل شده‌اند. در همین حد تغییر جهت کامپایل مجدد کدها کفایت می‌کند.
همچنین فایل‌های پروژه موجود را باز کرده و EntityFramework, Version=4.1.0.0 را جستجو کنید. نگارش جدید 4.4.0.0 است که باید اصلاح شود (این موارد را بهتر است توسط یک ادیتور معمولی خارج از VS.NET ویرایش کنید).
در زمان نگارش این مطلب EF Mini Profiler با EF 5 سازگار نیست. بنابراین اگر از آن استفاده می‌کنید نیاز است غیرفعالش کنید.


اولین استفاده از امکانات جدید MVC4:
استفاده از امکانات System.Web.Optimization که ذکر گردید، می‌تواند اولین تغییر مفید محسوب شود.
برای اینکه با نحوه کار آن بهتر آشنا شوید، یک پروژه جدید MVC4 را در VS.NET (از نوع basic) آغاز کنید. به صورت خودکار یک پوشه جدید را به نام App_Start به ریشه پروژه اضافه می‌کند. داخل آن فایل مثال BundleConfig قرار دارد. این کلاس در فایل global.asax برنامه نیز ثبت شده‌است. باید دقت داشت در حالت دیباگ (compilation debug=true در وب کانفیگ) تغییر خاصی را ملاحظه نخواهید کرد.
تمام این‌ها خوب؛ اما من به نحو زیر از این امکان جدید استفاده می‌کنم:
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Optimization;

namespace Common.WebToolkit
{
    /// <summary>
    /// A custom bundle orderer (IBundleOrderer) that will ensure bundles are 
    /// included in the order you register them.
    /// </summary>
    public class AsIsBundleOrderer : IBundleOrderer
    {
        public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
        {
            return files;
        }
    }

    public static class BundleConfig
    {
        private static void addBundle(string virtualPath, bool isCss, params string[] files)
        {
            BundleTable.EnableOptimizations = true;

            var existing = BundleTable.Bundles.GetBundleFor(virtualPath);
            if (existing != null)
                return;

            var newBundle = isCss ? new Bundle(virtualPath, new CssMinify()) : new Bundle(virtualPath, new JsMinify());
            newBundle.Orderer = new AsIsBundleOrderer();

            foreach (var file in files)
                newBundle.Include(file);

            BundleTable.Bundles.Add(newBundle);
        }

        public static IHtmlString AddScripts(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Render(virtualPath);
        }

        public static IHtmlString AddStyles(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Render(virtualPath);
        }

        public static IHtmlString AddScriptUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Url(virtualPath);
        }

        public static IHtmlString AddStyleUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Url(virtualPath);
        }
    }
}
کلاس BundleConfig فوق را به مجموعه کلاس‌های کمکی خود اضافه کنید.
چند نکته مهم در این کلاس وجود دارد:
الف) توسط AsIsBundleOrderer فایل‌ها به همان ترتیبی که به سیستم اضافه می‌شوند، در حاصل نهایی ظاهر خواهند شد. حالت پیش فرض مرتب سازی، بر اساس حروف الفباء است و ... خصوصا برای اسکریپت‌هایی که ترتیب معرفی آن‌ها مهم است، مساله ساز خواهد بود.
ب)BundleTable.EnableOptimizations سبب می‌شود تا حتی در حالت debug نیز فشرده سازی را مشاهده کنید.
ج) متدهای کمکی تعریف شده این امکان را می‌دهند تا بدون نیاز به کامپایل مجدد پروژه، به سادگی در کدهای Razor بتوانید اسکریپت‌ها را اضافه کنید.

 سپس نحوه جایگزینی تعاریف قبلی موجود در فایل‌های Razor با سیستم جدید، به نحو زیر است:
@using Common.WebToolkit

<link href="@BundleConfig.AddStyleUrl("~/Content/blueprint/print", "~/Content/blueprint/print.css")" rel="stylesheet" type="text/css" media="print"/>

@BundleConfig.AddScripts("~/Scripts/js",
                            "~/Scripts/jquery-1.8.0.min.js",
                            "~/Scripts/jquery.unobtrusive-ajax.min.js",
                            "~/Scripts/jquery.validate.min.js")

@BundleConfig.AddStyles("~/Content/css",
                            "~/Content/Site.css",
                            "~/Content/buttons.css")
پارامتر اول این متدها، سبب تعریف خودکار routing می‌شود. برای مثال اولین تعریف، آدرس خودکار زیر را تولید می‌کند:
http://site/Content/blueprint/print?v=hash
بنابراین تعریف دقیق آن مهم است. خصوصا اگر فایل‌های شما در پوشه‌ها و زیرپوشه‌های متعددی قرار گرفته نمی‌توان تمام آن‌ها را در طی یک مرحله معرفی نمود. هر سطح را باید از طریق یک بار معرفی به سیستم اضافه کرد. مثلا اگر یک زیر پوشه به نام noty دارید (Content/noty)، چون در یک سطح و زیرپوشه مجزا قرار دارد، باید نحوه تعریف آن به صورت زیر باشد:
@BundleConfig.AddStyles("~/Content/noty/css",
                                "~/Content/noty/jquery.noty.css",
                                "~/Content/noty/noty_theme_default.css")
این مورد خصوصا در مسیریابی تصاویر مرتبط با اسکریپت‌ها و شیوه نامه‌ها مؤثر است؛ وگرنه این تصاویر تعریف شده در فایل‌های CSS یافت نخواهند شد (تمام مثال‌های موجود در وب با این مساله مشکل دارند و فرض آن‌ها بر این است که کلیه فایل‌های خود را در یک پوشه، بدون هیچگونه زیرپوشه‌ای تعریف کرده‌اید).
پارامترهای بعدی، محل قرارگیری اسکریپت‌ها و CSSهای برنامه هستند و همانطور که عنوان شد اینبار با خیال راحت می‌توانید ترتیب معرفی خاصی را مدنظر داشته باشید؛ زیرا توسط AsIsBundleOrderer به صورت پیش فرض لحاظ خواهد شد.

 
بازخوردهای دوره
آشنایی با مدل برنامه نویسی TAP
خیر. در پشت صحنه از یک ماشین حالت (state machine) برای پیاده سازی async استفاده می‌کند. کل سطرهای بعدی تبدیل به یک IEnumerator می‌شوند که هر دستور آن شامل یک yield return است. هر مرحله که تمام شد، MoveNext این IEnumerator فراخوانی می‌شود تا به مرحله‌ی بعدی برسد. به این روش استفاده از coroutines هم گفته می‌شود که در سی شارپ 5، کامپایلر کار تولید کدهای آن‌را انجام می‌دهد. برای مطالعه بیشتر:
انجام پی در پی اعمال Async به کمک Iterators - قسمت اول  
انجام پی در پی اعمال Async به کمک Iterators - قسمت دوم  
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
ممنون، عالیه
فقط یه سوال:
چطور می‌تونیم یک Task خاص رو خودمون فراخوانی کنیم، مثلاً بعداً از اینکه کاربر در سایت ثبت‌نام کرد به صورت هفتگی براش ایمیل ارسال بشه یا مواردی از این دست، در مطلب بالا این طوری که من متوجه شدم، توسط دستور ScheduledTasksRegistry.Init(); در Application_Start تمام وظایف به صورت همزمان شروع میشن، در این حالت باید برای هر وظیفه یک کلاس برای ریجستر کردن اون بنویسیم؟ درسته؟
نظرات مطالب
معرفی و استفاده از DDL Triggers در SQL Server
باید از از حالت INSTEAD OF استفاده کنیم در DML Trigger ای که قراره نوشته بشه.
می‌توانیم در یک جدول از دیتابیس مان بر اساس یک شرط خاص, عملیات Insert,Delete,Update را مدیریت کنیم.
بعنوان مثال در قطعه کد زیر ما قبل از عملیات Insert در جدول tblTest چک میکنیم که اگر مقدار ستون FirstName برابر با null بود عملیات Insert آن رکورد در دیتابیس لغو شود.
ALTER TRIGGER [dbo].[Prevent_Befor_Insert_Null]
   ON  [dbTest].[dbo].[tblTest] 
   INSTEAD OF INSERT 
AS 
BEGIN
SET NOCOUNT ON
IF OBJECT_ID(N'dbTest.dbo.tblTest.FirstName') is null
BEGIN
DECLARE @Id int
SET @Id = (select Id from inserted)
RAISERROR ('مقدار فیلد نام نباید خالی باشد',16,1)
ROLLBACK
END
END

از دو طریق می‌توان به مقادیر فیلدهای رکورد جاری دسترس داشت:
1- استفاده از OBJECT_ID و ذکر نام فیلد مورد نظر
2- گرفتن فیلد مورد نظر از جدول INSERTED یا DELETED
DML Trigger‌ها دارای دو جدول خاص بنام‌های INSERTED و DELETED هستند که توسط خود SQL Server مدیریت می‌شوند.در حقیقت در پشت صحنه, ما با این دو جدول در هنگام تغییر مقادیر داده‌های جداول دیتابیس کار می‌کنیم و نمی‌توانیم بصورت مستقیم داده‌های جداول موجود در دیتا بیس مان را تغییر دهیم.
جدول INSERTED و DELETED حاوی رکورد جاری است که تحت تاثیر عمل درج, ویرایش و حذف در دیتابیس قرار گرفته است.
اطلاعات بیشتر در اینجا و اینجا
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
در حالت خاصی که در ادامه توضیح داده خواهد شد ممکن است بعضی از کنترلرها در لیست ظاهر نشوند، همانطور که در این کامنت به آن اشاره شده است.
ابتدا حالتی که باعث می‌شود بعضی از کنترلرهای شما در فرم مربوط به دسترسی‌های پویا ظاهرا نشود را توضیح و سپس راه حلی که باعث حل آن می‌شود را در ادامه خواهید خواند.
دو Area جدید به اسم‌های One , Two اضافه شدند که هر کدام شامل یک کنترلر به اسم Home  هستند (داخل هر کنترلر هم یک اکشن متد به نام Index وجود دارد )
    [Authorize(Policy = ConstantPolicies.DynamicPermission)]
    [Area("One")]
    [DisplayName("home")]
    public class HomeController : Controller
    {
        [DisplayName("ایندکس")]
        public IActionResult Index()
        {
            return View();
        }
    }

و مشابه همین برای Area دوم :
[Authorize(Policy = ConstantPolicies.DynamicPermission)]
    [Area("Two")]
    [DisplayName("home2")]
    public class HomeController : Controller
    {
        [DisplayName("ایندکس")]
        public IActionResult Index()
        {
            return View();
        }
    }
خروجی نمایش داده شده در صفحه مربوطه به دسترسی‌ها به صورت زیر می‌باشد که فقط یکی از این دو کنترلر در خروجی نمایش داده می‌شود:


بعد از بررسی کدهای مربوط به GetAllSecuredControllerActionsWithPolicy  متوجه شدم زمانی که دو کنترلر هم نام پشت سر هم قرار گرفته باشند،  کنترلر دوم بخاطر شرط زیر نادیده گرفته می‌شود:
 if (!lastControllerName.Equals(descriptor.ControllerName, StringComparison.Ordinal))
{
}
با فرض اینکه اول کنترلر مربوط به Area One اجرا شود، مقدار آن در متغییر lastConrolerName قرار میگیرد، در اجرای بعدی foreach چون کنترلر بعدی به همان اسم می‌باشد از اجرا این شرط صرف نظر میکند.
برای حل این مورد، namespace هر  کنترلر را برای منحصر بفرد کردن کنترلرها دخیل کردم:
   lastControllerName = $"{descriptor.ControllerTypeInfo.Namespace}{descriptor.ControllerName}";
و در دستور if هم به همین صورت عمل کردم :
       var controllerName = $"{descriptor.ControllerTypeInfo.Namespace}{descriptor.ControllerName}";
        if (!lastControllerName.Equals(controllerName, StringComparison.Ordinal))

* اگر هردو کنترلر‌های Home مربوط به area‌های one,two بعد از کنترلری به اسم Home باشند، در حالت عادی هیچ کدام از این کنترلرها در خروجی ظاهرا نخواهند شد، این مورد دقیقا مشکلی بود در پروژه جاری ام با آن برخورد کردم.
نظرات مطالب
EF Code First #1
برای اینکه بانک اطلاعاتی به طور خودکار تشکیل بشه،اجرای هر دستوری که به بانک مربوط میشه کافیه؟مثلا دستور select ؟
بعد از اون به ازای هربار اجرای یک دستور، وجود یا عدم وجود DB چک میشه؟
مطالب
Blazor 5x - قسمت دوم - بررسی ساختار اولیه‌ی پروژه‌های Blazor
پس از آشنایی با دو مدل هاستینگ برنامه‌های Blazor در قسمت قبل، اکنون می‌خواهیم ساختار ابتدایی قالب‌های این دو پروژه را بررسی کنیم.


ایجاد پروژه‌های خالی Blazor

در انتهای قسمت قبل، با روش ایجاد پروژه‌های خالی Blazor به کمک NET SDK 5x. آشنا شدیم. به همین جهت دو پوشه‌ی جدید BlazorWasmSample و BlazorServerSample را ایجاد کرده و از طریق خط فرمان و با کمک NET CLI.، در پوشه‌ی اولی دستور dotnet new blazorwasm و در پوشه‌ی دومی دستور dotnet new blazorserver را اجرا می‌کنیم.
البته اجرای این دو دستور، نیاز به اتصال به اینترنت را هم برای بار اول دارند؛ تا فایل‌های مورد نیاز و بسته‌های مرتبط را دریافت و restore کنند. بسته به سرعت اینترنت، حداقل یک ربعی را باید صبر کنید تا دریافت ابتدایی بسته‌های مرتبط به پایان برسد. برای دفعات بعدی، از کش محلی NuGet، برای restore بسته‌های blazor استفاده می‌شود که بسیار سریع است.


بررسی ساختار پروژه‌ی Blazor Server و اجرای آن

پس از اجرای دستور dotnet new blazorserver در یک پوشه‌ی خالی و ایجاد پروژه‌ی ابتدایی آن:


همانطور که مشاهده می‌کنید، ساختار این پروژه، بسیار شبیه به یک پروژه‌ی استاندارد ASP.NET Core از نوع Razor pages است.
- در پوشه‌ی properties آن، فایل launchSettings.json قرار دارد که برای نمونه، شماره پورت اجرایی برنامه را در حالت اجرای توسط دستور dotnet run و یا توسط IIS Express مشخص می‌کند.

- پوشه‌ی wwwroot آن، مخصوص ارائه‌ی فایل‌های ایستا مانند wwwroot\css\bootstrap است. در ابتدای کار، این پوشه به همراه فایل‌های CSS بوت استرپ است. در ادامه اگر نیاز باشد، فایل‌های جاوا اسکریپتی را نیز می‌توان به این قسمت اضافه کرد.

- در پوشه‌ی Data آن، سرویس تامین اطلاعاتی اتفاقی قرار دارد؛ به نام WeatherForecastService که هدف آن، تامین اطلاعات یک جدول نمایشی است که در ادامه در آدرس https://localhost:5001/fetchdata قابل مشاهده است.

- در پوشه‌ی Pages، تمام کامپوننت‌های Razor برنامه قرار می‌گیرند. یکی از مهم‌ترین صفحات آن، فایل Pages\_Host.cshtml است. کار این صفحه‌ی ریشه، افزودن تمام فایل‌های CSS و JS، به برنامه‌است. بنابراین در آینده نیز از همین صفحه برای افزودن فایل‌های CSS و JS استفاده خواهیم کرد. اگر به قسمت body این صفحه دقت کنیم، تگ جدید کامپوننت قابل مشاهده‌است:
<body>
   <component type="typeof(App)" render-mode="ServerPrerendered" />
کار آن، رندر کامپوننت App.razor واقع در ریشه‌ی پروژه‌است.
همچنین در همینجا، تگ‌های دیگری نیز قابل مشاهده هستند:
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
همانطور که در قسمت قبل نیز عنوان شد، اتصال برنامه‌ی در حال اجرای در مرورگر blazor server با backend، از طریق یک اتصال دائم SignalR صورت می‌گیرد. اگر در این بین خطای اتصالی رخ دهد، div با id مساوی blazor-error-ui فوق، یکی از پیام‌‌هایی را که مشاهده می‌کنید، بسته به اینکه برنامه در حالت توسعه در حال اجرا است و یا در حالت ارائه‌ی نهایی، نمایش می‌دهد. افزودن مدخل blazor.server.js نیز به همین منظور است. کار آن مدیریت اتصال دائم SignalR، به صورت خودکار است و از این لحاظ نیازی به کدنویسی خاصی از سمت ما ندارد. این اتصال، کار به روز رسانی UI و هدایت رخ‌دادها را به سمت سرور، انجام می‌دهد.

- در پوشه‌ی Shared، یکسری فایل‌های اشتراکی قرار دارند که قرار است در کامپوننت‌های واقع در پوشه‌ی Pages مورد استفاه قرار گیرند. برای نمونه فایل Shared\MainLayout.razor، شبیه به master page برنامه‌های web forms است که قالب کلی سایت را مشخص می‌کند. داخل آن Body@ را مشاهده می‌کنید که به معنای نمایش صفحات دیگر، دقیقا در همین محل است. همچنین در این پوشه فایل Shared\NavMenu.razor نیز قرار دارد که ارجاعی به آن در MainLayout.razor ذکر شده و کار آن نمایش منوی آبی‌رنگ سمت چپ صفحه‌است.

- در پوشه‌ی ریشه‌ی برنامه، فایل Imports.razor_ قابل مشاهده‌است. مزیت تعریف usingها در اینجا این است که از تکرار آن‌ها در کامپوننت‌های razor ای که در ادامه تهیه خواهیم کرد، جلوگیری می‌کند. هر using تعریف شده‌ی در اینجا، در تمام کامپوننت‌ها، قابل دسترسی است؛ به آن global imports هم گفته می‌شود.

- در پوشه‌ی ریشه‌ی برنامه، فایل App.razor نیز قابل مشاهده‌است. کار آن تعریف قالب پیش‌فرض برنامه‌است که برای مثال به Shared\MainLayout.razor اشاره می‌کند. همچنین کامپوننت مسیریابی نیز در اینجا ذکر شده‌است:
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
اگر شخصی مسیر از پیش تعریف شده‌ای را وارد کند، به قسمت Found وارد می‌شود و در غیر اینصورت به قسمت NotFound. در قسمت Found است که از قالب MainLayout برای رندر یک کامپوننت توسط کامپوننت RouteView، استفاده خواهد شد و در قسمت NotFound، فقط پیام «Sorry, there's nothing at this address» به کاربر نمایش داده می‌شود. قالب‌های هر دو نیز قابل تغییر و سفارشی سازی هستند.

- فایل appsettings.json نیز همانند برنامه‌های استاندارد ASP.NET Core در اینجا مشاهده می‌شود.

- فایل Program.cs آن که نقطه‌ی آغازین برنامه‌است و کار فراخوانی Startup.cs را انجام می‌دهد، دقیقا با یک فایل Program.cs برنامه‌ی استاندارد ASP.NET Core یکی است.

- در فایل Startup.cs آن، همانند قبل دو متد تنظیم سرویس‌ها و تنظیم میان‌افزارها قابل مشاهده‌است.
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
}
در ConfigureServices آن، سرویس‌های صفحات razor و ServerSideBlazor اضافه شده‌اند. همچنین سرویس نمونه‌ی WeatherForecastService نیز در اینجا ثبت شده‌است.
قسمت‌های جدید متد Configure آن، ثبت مسیریابی توکار BlazorHub است که مرتبط است با اتصال دائم SignalR برنامه و اگر مسیری پیدا نشد، به Host_ هدایت می‌شود:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...

    app.UseEndpoints(endpoints =>
    {
       endpoints.MapBlazorHub();
       endpoints.MapFallbackToPage("/_Host");
    });
}
در ادامه در همان پوشه، دستور dotnet run را اجرا می‌کنیم تا پروژه، کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.


که به همراه 13 درخواست و نزدیک به 600 KB دریافت اطلاعات از سمت سرور است.

این برنامه‌ی نمونه، به همراه سه صفحه‌ی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شده‌است. اگر صفحه‌ی شمارشگر آن‌را باز کنیم، با کلیک بر روی دکمه‌ی آن، هرچند مقدار current count افزایش می‌یابد، اما شاهد post-back متداولی به سمت سرور نیستیم و این صفحه بسیار شبیه به صفحات برنامه‌های SPA (تک صفحه‌ای وب) به نظر می‌رسد:


همانطور که عنوان شد، مدخل blazor.server.js فایل Pages\_Host.cshtml، کار به روز رسانی UI و هدایت رخ‌دادها را به سمت سرور به صورت خودکار انجام می‌دهد. به همین جهت است که post-back ای را مشاهده نمی‌کنیم و برنامه، شبیه به یک برنامه‌ی SPA به نظر می‌رسد؛ هر چند تمام رندرهای آن سمت سرور انجام می‌شوند و توسط SignalR به سمت کلاینت بازگشت داده خواهند شد.
برای نمونه اگر بر روی دکمه‌ی شمارشگر کلیک کنیم، در برگه‌ی network مرورگر، هیچ اثری از آن مشاهده نمی‌شود (هیچ رفت و برگشتی را مشاهده نمی‌کنیم). علت اینجا است که اطلاعات متناظر با این کلیک، از طریق web socket باز شده‌ی توسط SignalR، به سمت سرور ارسال شده و نتیجه‌ی واکنش به این کلیک‌ها و رندر HTML نهایی سمت سرور آن، از همین طریق به سمت کلاینت بازگشت داده می‌شود.


بررسی ساختار پروژه‌ی Blazor WASM و اجرای آن

پس از اجرای دستور dotnet new blazorwasm در یک پوشه‌ی خالی و ایجاد پروژه‌ی ابتدایی آن:


همان صفحات پروژه‌ی خالی Blazor Server در اینجا نیز قابل مشاهده هستند. این برنامه‌ی نمونه، به همراه سه صفحه‌ی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شده‌است. صفحات و کامپوننت‌های پوشه‌های Pages و Shared نیز دقیقا همانند پروژه‌ی Blazor Server قابل مشاهده هستند. مفاهیمی مانند فایل‌های Imports.razor_ و App.razor نیز مانند قبل هستند.

البته در اینجا فایل Startup ای مشاهده نمی‌شود و تمام تنظیمات آغازین برنامه، داخل فایل Program.cs انجام خواهند شد:
namespace BlazorWasmSample
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

            await builder.Build().RunAsync();
        }
    }
}
در اینجا ابتدا کامپوننت App.razor را به برنامه معرفی می‌کند که ساختار آن با نمونه‌ی مشابه Blazor Server دقیقا یکی است. سپس سرویسی را برای دسترسی به HttpClient، به سیستم تزریق وابستگی‌های برنامه معرفی می‌کند. هدف از آن، دسترسی ساده‌تر به endpoint‌های یک ASP.NET Core Web API است. از این جهت که در یک برنامه‌ی سمت کلاینت، دیگر دسترسی مستقیمی به سرویس‌های سمت سرور را نداریم و برای کار با آن‌ها همانند سایر برنامه‌های SPA که از Ajax استفاده می‌کنند، در اینجا از HttpClient برای کار با Web API‌های مختلف استفاده می‌شود.

تفاوت ساختاری دیگر این پروژه‌ی WASM، با نمونه‌ی Blazor Server، ساختار پوشه‌ی wwwroot آن است:


که به همراه فایل جدید نمونه‌ی wwwroot\sample-data\weather.json است؛ بجای سرویس متناظر سمت سرور آن در برنامه‌ی blazor server. همچنین فایل جدید wwwroot\index.html نیز قابل مشاهده‌است و محتوای تگ body آن به صورت زیر است:
<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>
- چون این برنامه، یک برنامه‌ی سمت کلاینت است، اینبار بجای فایل Host_ سمت سرور، فایل index.html سمت کلاینت را برای ارائه‌ی آغازین برنامه داریم که وابستگی به دات نت ندارد و توسط مرورگر قابل درک است.
- در ابتدای بارگذاری برنامه، یک loading نمایش داده می‌شود که در اینجا نحوه‌ی تعریف آن مشخص است. همچنین اگر خطایی رخ دهد نیز توسط div ای با id مساوی blazor-error-ui اطلاع رسانی می‌شود.
- مدخل blazor.webassembly.js در اینجا، کار دریافت وب اسمبلی و فایل‌های NET runtime. را انجام می‌دهد؛ برخلاف برنامه‌های Blazor Server که توسط فایل blazor.server.js، یک ارتباط دائم SignalR را با سرور برقرار می‌کردند تا کدهای رندر شده‌ی سمت سرور را دریافت و نمایش دهند و یا اطلاعاتی را به سمت سرور ارسال کنند: برای مثال بر روی دکمه‌ای کلیک شده‌است، اطلاعات مربوطه را در سمت سرور پردازش کن و نتیجه‌ی نهایی رندر شده را بازگشت بده. اما در اینجا همه چیز داخل مرورگر اجرا می‌شود و برای این نوع اعمال، رفت و برگشتی به سمت سرور صورت نمی‌گیرد. به همین جهت تمام کدهای #C ما به سمت کلاینت ارسال شده و داخل مرورگر به کمک فناوری وب اسمبلی، اجرا می‌شوند. در اینجا از لحاظ ارسال تمام کدهای مرتبط با UI برنامه‌ی سمت کلاینت به مرورگر کاربر، تفاوتی با فریم‌ورک‌هایی مانند Angular و یا React نیست و آن‌ها هم تمام کدهای UI برنامه را کامپایل کرده و یکجا ارسال می‌کنند.

در ادامه در همان پوشه، دستور dotnet run را اجرا می‌کنیم تا پروژه کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.


که به همراه 205 درخواست و نزدیک به 9.6 MB دریافت اطلاعات از سمت سرور است. البته اگر همین صفحه را refresh کنیم، دیگر شاهد دریافت مجدد فایل‌های DLL مربوط به NET Runtime. نخواهیم بود و اینبار از کش مرورگر خوانده می‌شوند:


در این برنامه‌ی سمت کلاینت، ابتدا تمام فایل‌های NET Runtime. و وب اسمبلی دریافت شده و سپس اجرای تغییرات UI، در همین سمت و بدون نیاز به اتصال دائم SignalR ای به سمت سرور، پردازش و نمایش داده می‌شوند. به همین جهت زمانیکه بر روی دکمه‌ی شمارشگر آن کلیک می‌کنیم، اتفاقی در برگه‌ی network مرورگر ثبت نمی‌شود و رفت و برگشتی به سمت سرور صورت نمی‌گیرد.

عدم وجود اتصال SignalR، مزیت امکان اجرای آفلاین برنامه‌ی WASM را نیز میسر می‌کند. برای مثال یکبار دیگر همان برنامه‌ی Blazor Server را به کمک دستور dotnet run اجرا کنید. سپس آن‌را در مرورگر در آدرس https://localhost:5001 باز کنید. اکنون پنجره‌ی کنسولی که dotnet run را اجرا کرده، خاتمه دهید (قسمت اجرای سمت سرور آن‌را ببندید).


بلافاصله تصویر «سعی در اتصال مجدد» فوق را مشاهده خواهیم کرد که به دلیل قطع اتصال SignalR رخ داده‌است. یعنی یک برنامه‌ی Blazor Server، بدون این اتصال دائم، قادر به ادامه‌ی فعالیت نیست. اما چنین محدودیتی با برنامه‌های Blazor WASM وجود ندارد.
البته بدیهی است اگر یک Web API سمت سرور برای ارائه‌ی اطلاعاتی به برنامه‌ی WASM نیاز باشد، API سمت سرور برنامه نیز باید جهت کار و نمایش اطلاعات در سمت کلاینت در دسترس باشد؛ وگرنه قسمت‌های متناظر با آن، قادر به نمایش و یا ثبت اطلاعات نخواهند بود.
مطالب
Angular CLI - قسمت دوم - ایجاد یک برنامه‌ی جدید
اولین کاری را که می‌توان پس از نصب Angular CLI انجام داد، ایجاد یک برنامه‌ی جدید است و نمونه‌ای از آن‌را در قسمت قبل بررسی کردیم. در ادامه می‌خواهیم به پارامترهای بیشتر مرتبط با آن و همچنین نحوه‌ی سفارشی سازی ایجاد برنامه‌های جدید بپردازیم.


ایجاد برنامه‌های جدید توسط Angular CLI

دستور خط فرمان ابتدایی ایجاد یک برنامه‌ی جدید توسط Angular CLI به صورت ذیل است
> ng new my-app
در اینجا ng همان Angular CLI است. new عملی است که قرار است رخ دهد و my-app یک نام دلخواه می‌باشد.
پس از اجرای این دستور، برنامه‌ی جدید ایجاد شده، در پوشه‌ی جدید my-app قرار می‌گیرد.

گزینه‌ی دیگر این دستور، استفاده از پرچم dry-run است:
> ng new my-app --dry-run
کار این پرچم صرفا گزارش دادن جزئیات عملیات ng new است؛ بدون اینکه فایلی را تولید کند. به این ترتیب می‌توان برآوردی را از فایل‌های تولید شده‌ی توسط فرامین ng، پیش از تولید واقعی آن‌ها، مشاهده کرد. برای مثال:
>ng new my-app --dry-run
installing ng
You specified the dry-run flag, so no changes will be written.
  create .editorconfig
  create README.md
  create src\app\app.component.css
  create src\app\app.component.html
.
.
.
Project 'my-app' successfully created.
همانطور که مشاهده می‌کنید، در ابتدای کار پیامی را مبنی بر عدم نوشته شدن این فایل‌ها بر روی فایل سیستم، ارائه داده‌است.

گزینه‌ی دیگر دستور ng new را که در قسمت قبل ملاحظه کردید:
> ng new my-app --skip-install
کار پرچم skip-install عدم فراخوانی خودکار دستور npm install است که سبب خواهد شد، برنامه بدون نصب وابستگی‌های npm آن، با سرعت هرچه بیشتر، ایجاد شود. از این گزینه می‌توان جهت مشاهده‌ی ساختار فایل‌های تولیدی استفاده کرد و در نهایت در این حالت باید دستور npm install را به صورت دستی فراخوانی کرد. پرچم dry-run نیز skip-install را به صورت ضمنی به همراه دارد.

برای مشاهده‌ی سایر پرچم‌های مرتبط با دستور ng new می‌توان از پرچم help استفاده کرد:
> ng new --help


بررسی فایل angular-cli.json.

فایل angular-cli.json‌. حاوی تنظیمات Angular CLI است.
در ابتدای این فایل، نام برنامه‌ی جدید را مشاهده می‌کنید. این نام، همانی است که توسط دستور ng new my-app تعیین گردید.
"project": {
      "name": "my-app"
  },
سپس محل پوشه‌ی source برنامه و همچنین خروجی نهایی آن، مشخص می‌شوند:
"apps": [
{
   "root": "src",
   "outDir": "dist",

یکی از تنظیمات مهم این فایل، مقدار prefix است:
"prefix": "app",
از این مقدار برای تنظیم مقدار prefix تمام کامپوننت‌ها و دایرکتیوهای تولیدی توسط Angular CLI استفاده می‌شود. برای مثال اگر به فایل src\app\app.component.ts دقت کنید:
@Component({
  selector: 'app-root',
نام selector آن با app- شروع شده‌است که این app، از مقدار prefix فایل angular-cli.json‌. دریافت شده‌است.
تغییر این مقدار صرفا بر روی کامپوننت‌های جدید تولید شده‌ی توسط Angular CLI تاثیرگذار خواهند بود. اگر می‌خواهید در ابتدای کار تولید یک برنامه، این مقدار را مشخص کنید، می‌توان از پرچم prefix استفاده کرد و در صورت عدم ذکر آن، مقدار پیش فرض app برای آن درنظر گرفته می‌شود:
> ng new my-project --skip-install --prefix myCompany


عدم ایجاد مخزن Git به همراه ng new

با صدور فرمان ng new، کار ایجاد یک مخزن Git نیز به صورت خودکار انجام خواهد شد. برای نمونه اگر خواستید برنامه‌ای را بدون نصب وابستگی‌ها، بدون ایجاد تست‌ها و بدون ایجاد مخزن git آن تولید کنید، می‌توان از دستور ذیل استفاده کرد:
> ng new my-project --skip-install --skip-git --skip-tests --skip-commit


استفاده‌ی از sass بجای css توسط Angular CLI

سیستم Build همراه با Angular CLI مبتنی بر webpack است و به خوبی قابلیت پردازش فایل‌های sass را نیز دارا است. اگر خواستید حالت پیش فرض تولید فایل‌های css این ابزار را که در فایل angular-cli.json‌. نمونه‌ای از آن ذکر شده‌است، به همراه فایل‌هایی مانند app.component.css، به sass تغییر دهید:
"styles": [
     "styles.css"
],

"defaults": {
   "styleExt": "css",
   "component": {}
  }
تنها کافی است پرچم style را با sass مقدار دهی کرد که حالت پیش فرض آن css است:
> ng new my-project --skip-install --style sass
با ذکر این پرچم، تغییرات فایل angular-cli.json‌  به صورت ذیل خواهند بود:
"styles": [
    "styles.sass"
],
"defaults": {
   "styleExt": "sass",
   "component": {}
}
و حتی کامپوننت src\app\app.component.ts نیز به همراه شیوه‌نامه‌ای از نوع sass که در حین ارائه نهایی توسط webpack به صورت خودکار پردازش می‌شود (بدون نیاز به تنظیمات اضافه‌تری)، مقدار دهی شده‌است:
@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.sass']
})
و یا حتی اگر علاقمند به استفاده‌ی از less باشید نیز می‌توان پرچم style less-- را استفاده نمود.


انجام تنظیمات مسیریابی پیش فرض پروژه جدید توسط Angular CLI

حالت پیش فرض تولید برنامه‌های جدید Angular CLI به همراه تنظیمات مسیریابی آن نیست. اگر علاقمند هستید تا مبحث مسیریابی را خلاصه کرده و به سرعت تنظیمات ابتدایی مسیریابی را توسط این ابزار تولید کنید، می‌توان پرچم routing را نیز در اینجا ذکر کرد:
> ng new my-project --skip-install --routing
در این حالت اگر به پوشه‌ی src\app مراجعه کنید، فایل جدید app-routing.module.ts را نیز مشاهده خواهید کرد که AppRoutingModule پیش فرضی در آن تنظیم شده‌است و آماده‌ی استفاده می‌باشد.
const routes: Routes = [
  {
     path: '',
     children: []
  }
];
همچنین فایل app.module.ts را نیز اندکی تغییر داده و این AppRoutingModule جدید را نیز به آن معرفی کرده‌است.
imports: [
   BrowserModule,
   FormsModule,
   HttpModule,
   AppRoutingModule
],
به این ترتیب ارتباطات ابتدایی مورد نیاز سیستم مسیریابی برقرار شده و قابل استفاده‌است. بنابراین ذکر پرچم routing می‌تواند یکی از پرچم‌های اصلی ایجاد برنامه‌های جدید مبتنی بر Angular CLI باشد.


اجرای ابتدایی یک برنامه‌ی مبتنی بر Angular CLI

پس از انتخاب پرچم‌های مناسب جهت ایجاد یک پروژه‌ی جدید مبتنی بر Angular CLI و همچنین نصب وابستگی‌های آن‌ها و یا عدم ذکر پرچم skip-install، اکنون نوبت به اجرای این پروژه‌است. به همین جهت از طریق خط فرمان به ریشه‌ی پوشه‌ی برنامه‌ی جدید ایجاد شده، وارد شوید. سپس دستور ذیل را صادر کنید:
>ng serve -o
در اینجا o- به معنای open است؛ یا گشودن آن در یک مرورگر. به این ترتیب کار کامپایل برنامه صورت گرفته و توسط مرورگر پیش‌فرض سیستم به صورت خودکار باز خواهد شد. آدرس پیش فرض آن نیز به صورت ذیل است:
 http://localhost:4200/


نکته‌ی جالب این وب سرور در این است که تغییرات شما را به صورت خودکار دنبال کرده و بلافاصله ارائه می‌دهد. برای مثال فایل src\app\app.component.html را گشوده و به صورت ذیل تغییر دهید:
 <h1>
Test
  {{title}}
</h1>
پس از ذخیره‌ی آن، بلافاصله خروجی نهایی را در مرورگر خواهید دید.


تغییر پیش فرض‌های عمومی Angular CLI

تا اینجا مشاهده کردیم که اگر بخواهیم مقدار prefix پیش فرض را که به app تنظیم شده‌است به myCompany تغییر دهیم، یا می‌توان از پرچم prefix در ابتدای کار فراخوانی دستور ng new استفاده کرد و یا می‌توان فایل angular-cli.json. را نیز دستی ویرایش نمود. برای تغییر عمومی و سراسری مقدار پیش فرض app می‌توان از دستور ng set استفاده کرد:
>ng set apps.prefix myCompany
>ng set apps.prefix myCompany -g
دستور اول فایل angular-cli.json. پروژه‌ی جاری را ویرایش می‌کند و دستور دوم، فایل angular-cli.json سراسری Angular CLI را مقدار دهی خواهد کرد (با توجه به سوئیچ g- آن). البته بدیهی است ویرایشگرهای امروزی به خوبی قابلیت ویرایش فایل‌های json را ارائه می‌دهند و شاید نیازی به استفاده‌ی از این دستورات،‌حداقل برای اعمال تغییرات محلی نباشد.
و یا اگر بخواهید نوع شیوه‌نامه‌ی مورد استفاده را ویرایش کنید، می‌توان از یکی از دو دستور ذیل استفاده کرد (اولی محلی است و دومی عمومی):
>ng set defaults.styleExt sass
>ng set defaults.styleExt sass -g


اجرای امکانات Linting پروژه‌های مبتنی بر Angular CLI

برای بررسی کیفیت کدهای نوشته شده، می‌توان از امکانات Linting استفاده کرد. برای این منظور تنها کافی است دستور ذیل را در ریشه‌ی پروژه اجرا نمود:
> ng lint
برای مشاهده‌ی تمام گزینه‌های آن دستور زیر را صادر کنید:
> ng lint --help
اگر علاقمند هستید تا خروجی این ابزار، با رنگ‌های بهتری نمایش داده شوند، می‌توان از دستور ذیل استفاده کرد:
> ng lint --format stylish
به علاوه این ابزار قابلیت رفع مشکلات را با اعمال تغییراتی در کدهای موجود نیز به همراه دارد:
>ng lint --fix
برای این منظور می‌توان از پرچم fix آن استفاده کرد.

یک مثال: فایل src\app\app.component.ts‌  را باز کنید و به عمد تعدادی مشکل را در آن ایجاد نمائید. برای نمونه دو سطر ابتدایی آن‌را به صورت ذیل تغییر دهید:
import { Component } from '@angular/core'
let number = 10;
اکنون اگر ng lint را اجرا کنیم، به خروجی ذیل خواهیم رسید:
>ng lint --format stylish

/src/app/app.component.ts[3, 5]: Identifier 'number' is never reassigned; use 'const' instead of 'let'.
/src/app/app.component.ts[1, 42]: Missing semicolon

Lint errors found in the listed files.
عنوان می‌کند که بهتر است number به صورت یک const تعریف شود و همچنین یک سمی‌کالن در سطر اول فراموش شده‌است.
اکنون اگر دستور ng lint --fix را فراخوانی کنیم، تغییرات ذیل به فایل src\app\app.component.ts اعمال خواهند شد:
import { Component } from '@angular/core';
const number = 10;
به صورت خودکار، به سطر اول، یک سمی‌کالن را اضافه کرده و همچنین سطر دوم را نیز به const تبدیل کرده‌است.