مطالب
Defensive Programming - بازگشت نتایج قابل پیش بینی توسط متدها
در این مطلب یکی از اهداف Defensive Programming تحت عنوان Predictability مرتبط با متدها را بررسی کرده و تمرکز اصلی، بر روی مقدار بازگشتی متدها خواهد بود. 
پیش نیازها
به طور کلی، نتیجه حاصل از اجرای یک متد می‌تواند یکی از حالت‌های زیر باشد:

متدی تحت عنوان ValidateEmail را تصور کنید. این متد از حیث بازگشت نتیجه به عنوان خروجی می‌تواند به اشکال مختلفی پیاده سازی شود که در ادامه مشاهده می‌کنیم:


متد ValidateEmail با خروجی Boolean

        public bool ValidateEmail(string email)
        {
            var valid = true;
            if (string.IsNullOrWhiteSpace(email))
            {
                valid = false;
            }

            var isValidFormat = true;//todo: using RegularExpression
            if (!isValidFormat)
            {
                valid = false;
            }

            var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
            if (!isRealDoamin)
            {
                valid = false;
            }

            return valid;
        }

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

var email = "email@example.com";
var isValid = ValidateEmail(email);
if(isValid)
{
    //do something
}


متد ValidateEmail با صدور استثناء

        public void ValidateEmail(string email)
        {
            if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email));

            var isValidFormat = true;//todo: using RegularExpression
            if (!isValidFormat) throw new ArgumentException("email is not in a correct format");

            var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
            if (!isRealDoamin) throw new ArgumentException("email does not include a valid domain.")
        }

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


متد ValidateEmail با چندین خروجی


برای این منظور چندین راه حل پیش رو داریم.


با استفاده از پارامتر out:

        public bool ValidateEmail(string email, out string message)
        {
            var valid = true;
            message = string.Empty;

            if (string.IsNullOrWhiteSpace(email))
            {
                valid = false;
                message = "email is null.";
            }

            if (valid)
            {
                var isValidFormat = true;//todo: using RegularExpression
                if (!isValidFormat)
                {
                    valid = false;
                    message = "email is not in a correct format";
                }
            }

            if (valid)
            {
                var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
                if (!isRealDoamin)
                {
                    valid = false;
                    message = "email does not include a valid domain.";
                }
            }

            return valid;
        }
و نحوه استفاده از آن:
var email = "email@example.com";
var isValid = ValidateEmail(email, out string message);
if (isValid)
{
    //do something
}
خب کمی بهتر شد؛ ولی امکان دریافت لیست خطاهای اعتبارسنجی را به صورت یکجا نداریم و یک تک پیغام را در اختیار ما قرار می‌دهد. برای بهبود آن می‌توان از یک Tuple به شکل زیر برای تولید خروجی متد بالا نیز استفاده کرد.
Tuple<bool, List<string>> result = Tuple.Create<bool, List<string>>(true, new List<string>());
یا بهتر است یک کلاس مشخصی برای این منظور در نظر گرفت؛ به عنوان مثال:
        public class OperationResult
        {
            public bool Success { get; set; }
            public IList<string> Messages { get; } = new List<string>();

            public void AddMessage(string message)
            {
                Messages.Add(message);
            }
        }
در این صورت بدنه متد ValidateEmail به شکل زیر تغییر خواهد کرد:
        public OperationResult ValidateEmail(string email)
        {
            var result = new OperationResult();

            if (string.IsNullOrWhiteSpace(email))
            {
                result.Success = false;
                result.AddMessage("email is null.");
            }

            if (result.Success)
            {
                var isValidFormat = true;//todo: using RegularExpression
                if (!isValidFormat)
                {
                    result.Success = false;
                    result.AddMessage("email is not in a correct format");
                }
            }

            if (result.Success)
            {
                var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
                if (!isRealDoamin)
                {
                    result.Success = false;
                    result.AddMessage("email does not include a valid domain.");
                }
            }

            return result;
        }

این بار خروجی متد مذکور از نوع OperationResult ای می‌باشد که هم موفقیت آمیز بودن یا عدم آن را مشخص می‌کند و همچنین امکان دسترسی به لیست پیغام‌های مرتبط با اعتبارسنجی‌های انجام شده، وجود دارد.


استفاده از Exception برای نمایش پیغام برای کاربر نهایی

با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، می‌توان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشده‌اند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز می‌باشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع به عنوان یک Cross Cutting Concern تحت عنوان Exception Management آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت.

به عنوان مثال داشتن یک کلاس Exception سفارشی تحت عنوان UserFriendlyException در این راستا یک الزام می‌باشد.

   [Serializable]
   public class UserFriendlyException : Exception
   {
       public string Details { get; private set; }
       public int Code { get; set; }

       public UserFriendlyException()
       {
       }

       public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context)
           : base(serializationInfo, context)
       {

       }

       public UserFriendlyException(string message)
           : base(message)
       {
       }

       public UserFriendlyException(int code, string message)
           : this(message)
       {
           Code = code;
       }

       public UserFriendlyException(string message, string details)
           : this(message)
       {
           Details = details;
       }

       public UserFriendlyException(int code, string message, string details)
           : this(message, details)
       {
           Code = code;
       }

       public UserFriendlyException(string message, Exception innerException)
           : base(message, innerException)
       {
       }

       public UserFriendlyException(string message, string details, Exception innerException)
           : this(message, innerException)
       {
           Details = details;
       }
   }

و همچنین لازم است در بالاترین لایه سیستم خود به عنوان مثال برای یک پروژه ASP.NET MVC یا ASP.NET Core MVC می‌توان یک ExceptionFilter سفارشی نیز تهیه کرد که هم به صورت سراسری استثنا‌ءهای سفارشی شما را مدیریت کند و همچنین خروجی مناسب Json برای استفاده در سمت کلاینت را نیز مهیا کند. به عنوان مثال برای درخواست‌های Ajax ای لازم است در سمت کلاینت نیز پاسخ‌های رسیده از سمت سرور به صورت سراسری مدیریت شوند و برای سایر درخواست‌ها همان نمایش صفحات خطای پیغام مرتبط با استثناء رخ داده شده کفایت می‌کند.


یک مدل پیشنهادی برای تهیه خروجی مناسب برای ارسال جزئیات استثنا رخ داده در درخواست‌های Ajax ای

    [Serializable]
    public class MvcAjaxResponse : MvcAjaxResponse<object>
    {
        public MvcAjaxResponse()
        {
        }

        public MvcAjaxResponse(bool success)
            : base(success)
        {
        }

        public MvcAjaxResponse(object result)
            : base(result)
        {
        }

        public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false)
            : base(error, unAuthorizedRequest)
        {
        }
    }
   

    [Serializable]
    public class MvcAjaxResponse<TResult> : MvcAjaxResponseBase
    {
        public MvcAjaxResponse(TResult result)
        {
            Result = result;
            Success = true;
        }

        public MvcAjaxResponse()
        {
            Success = true;
        }

        public MvcAjaxResponse(bool success)
        {
            Success = success;
        }

        public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false)
        {
            Error = error;
            UnAuthorizedRequest = unAuthorizedRequest;
            Success = false;
        }

        /// <summary>
        ///     The actual result object of AJAX request.
        ///     It is set if <see cref="MvcAjaxResponseBase.Success" /> is true.
        /// </summary>
        public TResult Result { get; set; }
    }

    public class MvcAjaxResponseBase
    {
        public string TargetUrl { get; set; }

        public bool Success { get; set; }

        public ErrorInfo Error { get; set; }

        public bool UnAuthorizedRequest { get; set; }

        public bool __mvc { get; } = true;
    }

و کلاس  ErrorInfo:
    [Serializable]
    public class ErrorInfo
    {
        public int Code { get; set; }
        public string Message { get; set; }
        public string Detail { get; set; }
        public Dictionary<string, string> ValidationErrors { get; set; }

        public ErrorInfo()
        {
        }
        public ErrorInfo(string message)
        {
            Message = message;
        }
        public ErrorInfo(int code)
        {
            Code = code;
        }

        public ErrorInfo(int code, string message)
            : this(message)
        {
            Code = code;
        }

        public ErrorInfo(string message, string details)
            : this(message)
        {
            Detail = details;
        }

        public ErrorInfo(int code, string message, string details)
            : this(message, details)
        {
            Code = code;
        }
    }

یک مثال واقعی
        public async Task CheckIsDeactiveAsync(long id)
        {
            if (await _organizationalUnits.AnyAsync(a => a.Id == id && !a.IsActive).ConfigureAwait(false))
                throw new UserFriendlyException("واحد سازمانی جاری غیرفعال می‌باشد.");
        }

روش نام گذاری متدهایی که امکان بازگشت خروجی Null را دارند

متد زیر را در نظر بگیرید:
public User GetById(long id);
وظیفه این متد یافت و بازگشت یک وهله از کلاس User می‌باشد و نباید خروجی Null تولید کند. در صورتیکه در پیاده سازی آن امکان یافت چنین کاربری نبود، بهتر است یک استثنای سفارشی دیگر شبیه به EntityNotFoundException زیر را صادر کنید:
    [Serializable]
    public class EntityNotFoundException : Exception
    {
        public Type EntityType { get; set; }
        public object Id { get; set; }
        public EntityNotFoundException()
        {
        }

        public EntityNotFoundException(string message)
            : base(message)
        {

        }

        public EntityNotFoundException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        public EntityNotFoundException(SerializationInfo serializationInfo, StreamingContext context)
            : base(serializationInfo, context)
        {

        }
        public EntityNotFoundException(Type entityType, object id)
            : this(entityType, id, null)
        {

        }
        public EntityNotFoundException(Type entityType, object id, Exception innerException)
            : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException)
        {
            EntityType = entityType;
            Id = id;
        }

    }
یا اگر امکان بازگشت مقدار Null را داشته باشد، بهتر است نام آن به GetByIdOrNull تغییر یابد. در این صورت تکلیف استفاده کننده از این متد مشخص می‌باشد.

یک مثال واقعی 

        public async Task<UserOrganizationalUnitInfo> GetCurrentOrganizationalUnitInfoOrNullAsync(long userId)
        {
            return (await _setting.GetSettingValueForUserAsync(
                    UserSettingNames.CurrentOrganizationalUnitInfo, userId).ConfigureAwait(false))
                .FromJsonString<UserOrganizationalUnitInfo>();
        }

نظرات مطالب
استفاده از افزونه Typeahead مجموعه Twitter Bootstrap در ASP.NET MVC
matcher یک callback جاوا اسکریپتی است. بنابراین در سمت کلاینت باید پیاده سازی شود (چیزی شبیه به مقدار دهی source پویای مثال آخر بحث).
$('.typeahead').typeahead({
    matcher: function(item) {
        // آیتم مقداری است که باید برای تطابق بررسی شود
        //  this.query کوئری جاری را بر می‌گرداند.
        return true // اگر آیتم تطابق داشته است
    }
})
مثلا:
matcher: function (item) {
    if (item.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1) {
        return true;
    }
}
از matcher برای سفارشی سازی و بررسی اینکه آیا عبارت تایپ شده توسط کاربر با list دریافتی تطابق دارد یا خیر، استفاده می‌شود. حالت پیش فرض، تطابق دقیق لیست دریافتی با متن ورودی کاربر است. با استفاده از matcher در سمت کلاینت می‌توانید نحوه نمایش لیست دریافتی از سرور را سفارشی سازی کنید.
مسیرراه‌ها
ASP.NET MVC
              مطالب
              Persist ، Load و Bookmark در Workflow

              در خیلی از مواقع workflow‌ها به مرحله‌ای می‌رسند که احتیاج به دستوری از بیرون از فرآیند دارند. در هنگام انتظار، اگر به هر دلیلی workflow از حافظه حذف شود، امکان ادامه فرآیند وجود ندارد. اما می‌توان با Persist (ذخیره) کردن آن، در زمان انتظار و فراخوانی مجدد آن در هنگام نیاز، این ریسک را برطرف نمود.

              قصد دارم با این مثال، طریقه persist شدن یک workflow در زمانیکه نیاز به انتظار برای تایید دارد و فراخوانی آن از همان نقطه پس از تایید مربوطه را توضیح دهم.

              ساختار اینترفیس کاربری ما WPF می‌باشد. پس در ابتدا یک پروژه از نوع WPF ایجاد می‌کنیم. اسم solution  را PersistWF و اسم Project را PersistWF.UI انتخاب می‌کنیم.

              در پروژه  UI نام فایل MainWindow.xaml  را به AddRequest.xaml تغییر می‌دهیم. همچنین اسم کلاس مربوطه را در codebehind 

              همین طور مقدار StartupUri را هم در app.xaml اصلاح می‌کنیم

              StartupUri="AddRequest.xaml"

              Reference ‌های زیر رو هم به پروژه اضافه می‌کنیم 

              •System.Activities
              •System.Activities.DurableInstancing
              •System.Configuration
              •System.Data.Linq
              •System.Runtime.DurableInstancing
              •System.ServiceModel
              •System.ServiceModel.Activities
              •System.Workflow.ComponentModel
              •System.Runtime.DurableInstancing
              •System.Activities.DurableInstancing

              قرار است کاربری ثبت نام کند، در فرایند ثبت، منتظر تایید یکی از مدیران قرار می‌گیرد. مدیر، لیست کاربران جدید را می‌بینید، یک کاربر را انتخاب می‌کند؛ مقادیر لازم را وارد می‌کند و سپس پروسه تایید را انجام می‌دهد که فراخوانی فرآیند مربوطه از همان قسمتی‌است که منتظر تایید مانده است.

              برای Persist کردن workflow از کلاس SqlWorkflowInstanceStore   استفاده می‌کنم. این شی به connection ای به یک دیتابیس با یک ساختار معین احتیاج دارد. خوشبختانه اسکریپت‌های مورد نیاز این ساختار در پوشه [Drive]:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en وجود دارند. دو اسکریپت با نام‌های SqlWorkflowInstanceStoreSchema و SqlWorkflowInstanceStoreLogic باید به ترتیب در دیتابیس اجرا شوند.

              من یک دیتابیس با نام PersistWF ایجاد می‌کنم و اسکریپت‌ها را بر روی آن اجرا می‌کنم. یک جدول هم برای نگهداری کاربران ثبت شده در همین دیتابیس ایجاد می‌کنم.

              و شمایل دیتابیس ما پس از اجرا کردن اسکریپت‌ها و ساختن جدول User  بدین شکل است: 

              XAML زیر، ساختار فرم AddRequest می‌باشد که قرار است نقش UI برنامه را ایفا کند. آن را با XAML‌های پیش فرض عوض کنید. 

              <Window x:Class="PersistWF.UI.AddRequest"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      Title="MainWindow" Height="520" Width="550" Loaded="Window_Loaded">
                  <Grid MinWidth="300" MinHeight="100" Width="514">
                      <Label Height="30" Margin="5,10,10,10" Name="lblName"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="90"  HorizontalContentAlignment="Right">Name:</Label>
                      <Label Height="30" Margin="270,10,10,10" Name="lblPhone"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="90"  HorizontalContentAlignment="Right">Phone Number:</Label>
                      <Label Height="30" Margin="5,40,10,10" Name="lblEmail"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="90"  HorizontalContentAlignment="Right">Email:</Label>
                      <TextBox Height="25" Margin="100,10,10,10" Name="txtName"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="170" />
                      <TextBox Height="25" Margin="365,10,10,10" Name="txtPhone"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="100" />
                      <TextBox Height="25" Margin="100,40,10,10" Name="txtEmail"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="300" />
                      <Button Height="23" Margin="100,86,0,0" Name="brnRegister"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="70"  Click="brnRegister_Click">Register</Button>
                      <ListView x:Name="lstUsers" Margin="10,125,10,10" Height="145"  VerticalAlignment="Top" ItemsSource="{Binding}"  HorizontalContentAlignment="Center"  SelectionChanged="lstUsers_SelectionChanged" >
                          <ListView.View>
                              <GridView>
                                  <GridViewColumn Header="Current User" Width="480">
                                      <GridViewColumn.CellTemplate>
                                          <DataTemplate>
                                              <StackPanel Orientation="Horizontal">
                                                  <TextBlock Text="{Binding Name}"  Width="110"/>
                                                  <TextBlock Text="{Binding Phone}"  Width="70"/>
                                                  <TextBlock Text="{Binding Email}"  Width="130"/>
                                                  <TextBlock Text="{Binding Status}"  Width="70"/>
                                                  <TextBlock Text="{Binding AcceptedBy}"  Width="100"/>
                                              </StackPanel>
                                          </DataTemplate>
                                      </GridViewColumn.CellTemplate>
                                  </GridViewColumn>
                              </GridView>
                          </ListView.View>
                      </ListView>
                      <Label Height="37" HorizontalAlignment="Stretch" Margin="10,272,5,10"  Name="lblSelectedNotes" VerticalAlignment="Top" Visibility="Hidden" />
                      <Label Height="30" Margin="10,0,0,140" Name="lblAgent"  VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="40"  HorizontalContentAlignment="Left" Visibility="Hidden">Admin Name:</Label>
                      <TextBox Height="25" Margin="60,0,0,140" Name="txtAcceptedBy"  VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="190"  Visibility="Hidden" />
                      <Button Height="25" Margin="270,0,0,140" Name="btnAccept"  VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="90"  Click="btnAccept_Click" Visibility="Hidden">Accept</Button>
                      <Label Height="27" HorizontalAlignment="Left" Margin="10,0,0,110"  Name="lblEvent" VerticalAlignment="Bottom" Width="76">Event Log</Label>
                      <ListBox Margin="12,0,5,12" Name="lstEvents" Height="100"  VerticalAlignment="Bottom" FontStretch="Condensed" FontSize="10" />
                  </Grid>
              </Window>

              اگر همه چیز مرتب باشد؛ ساختار فرم شما باید به این شکل باشد 

              اکثر workflow‌ها از activity معروف  WrteLine استفاده می‌کنند که برای نمایش یک رشته به کار می‌رود. ما هم در workflow مثالمان از این Activity استفاده می‌کنیم. اما برای اینکه مقادیری که توسط این Activity ایجاد می‌شوند در کادر event log فرم خودمان نمایش داده شود؛ احتیاج داریم که یک TextWriter سفارشی برای خودمان ایجاد کنیم. اما قبل از آن یک کلاس static در پروژه ایجاد می‌کنیم که بتوانیم در هر قسمتی، به فرم دسترسی داشته باشیم.

              کلاسی را با نام ApplicationInterface به پروژه اضافه کرده و یک  Property استاتیک از جنس فرم AddRequest هم برای آن تعریف می‌کنیم:

              using System;
              using System.Collections.Generic;
              using System.Linq;
              using System.Text;
              using System.Threading.Tasks;
              
              namespace PersistWF.UI
              {
                  public static class ApplicationInterface
                  {
                      public static AddRequest _app { get; set; }
                  }
              }

              به Constructor کلاس موجود در فایل AddRequest.xaml.cs  این خط کد رو اضافه می‌کنم

                      public AddRequest()
                      {
                          InitializeComponent();
                          ApplicationInterface._app = this;
                      }
              این دو متد را هم به این کلاس اضافه می‌کنیم  
              private void AddEvent(string szText)
                      {
                          lstEvents.Items.Add(szText);
                      }
                      public ListBox GetEventListBox()
                      {
                          return this.lstEvents;
                      }

              متد اول برای اضافه کردن یک event Log و متد دوم هم که کنسول لاگ را در اختیار درخواست کننده‌اش قرار می‌دهد.

              و حالا کلاس TextWriter سفارشی‌امان را می‌نویسیم. یک کلاس به نام ListBoxTextWriter به پروژه اضافه می‌کنیم که از TextWriter مشتق می‌شود و محتویات آن‌را در زیر می‌بینید: 

              using System;
              using System.Collections.Generic;
              using System.IO;
              using System.Linq;
              using System.Text;
              using System.Threading.Tasks;
              using System.Windows.Controls;
              
              namespace PersistWF.UI
              {
                  public class ListBoxTextWriter : TextWriter 
                  { 
                       const string textClosed = "This TextWriter must be opened before use"; 
                       private Encoding _encoding; 
                       private bool _isOpen = false; 
                       private ListBox _listBox; 
                       public ListBoxTextWriter() 
                       { 
                           // Get the static list box 
                           _listBox = ApplicationInterface._app.GetEventListBox(); 
                           if (_listBox != null) 
                           _isOpen = true; 
                       } 
                       public ListBoxTextWriter(ListBox listBox) 
                       { 
                           this._listBox = listBox; 
                           this._isOpen = true; 
                       } 
                       public override Encoding Encoding 
                       { 
                           get 
                           { 
                              if (_encoding == null) 
                              { 
                                  _encoding = new UnicodeEncoding(false, false); 
                              } 
                              return _encoding; 
                           } 
                       }
                       public override void Close()
                       {
                           this.Dispose(true);
                       }
                       protected override void Dispose(bool disposing)
                       {
                           this._isOpen = false;
                           base.Dispose(disposing);
                       }
                       public override void Write(char value)
                       {
                           if (!this._isOpen)
                               throw new ApplicationException(textClosed); ;
                           this._listBox.Dispatcher.BeginInvoke(new Action(() => this._listBox.Items.Add(value.ToString())));
                       }
                       public override void Write(string value)
                       {
                           if (!this._isOpen)
                               throw new ApplicationException(textClosed); 
                           if (value != null)
                               this._listBox.Dispatcher.BeginInvoke(new Action(() => this._listBox.Items.Add(value)));
                       }
                       public override void Write(char[] buffer, int index, int count)
                       {
                           String toAdd = "";
                           if (!this._isOpen)
                               throw new ApplicationException(textClosed); ;
                           if (buffer == null || index < 0 || count < 0)
                               throw new ArgumentOutOfRangeException("buffer");
                           if ((buffer.Length - index) < count)
                               throw new ArgumentException("The buffer is too small");
                           for (int i = 0; i < count; i++)
                               toAdd += buffer[i];
                           this._listBox.Dispatcher.BeginInvoke(new Action(() => this._listBox.Items.Add(toAdd)));
                       }
                  }
              }

              همان طور که می‌بینید کلاس ListBoxTextWriter از کلاس abstract  TextWriter  مشتق شده و پیاده سازی از متد Write را فراهم می‌کند تا یک رشته را به کنترل ListBox اضافه کنه. (البته سه تا از این متد‌ها را Override می‌کنیم تا بتوانیم یک رشته، یک کاراکتر و یا آرایه ای از کاراکتر‌ها را به ListBox اضافه کنیم) در constructor  پیشفرض از کلاس ApplicationInterface استفاده کردیم تا بتوانیم کنترل lstEvents را از فرم اصلی برنامه به دست بیاوریم. برای Add کردن از Dispatcher و متد BeginInvoke مرتبط با آن استفاده کردیم . این کار، متد را قادر می‌سازد حتی وقتی‌که از یک thread متفاوت فراخوانی می‌شود، کار کند.

              حالا می‌توانیم از این کلاس، به عنوان مقدار خاصیت TextWriter برای WriteLine استفاده کنیم.

              به کلاس ApplicationInterface برگردیم تا متد زیر را هم به آن اضافه کنیم 

              public static void AddEvent(String status)
                      {
                          if (_app != null)
                          {
                              new ListBoxTextWriter(_app.GetEventListBox()).WriteLine(status);
                          }
                      }

              این هم از constructor دومی استفاده می‌کنه برای معرفی ListBox.

              برای ارتباط با دیتابیس از LINQ to SQL استفاده می‌کنیم تا User رو ذخیره و بازیابی کنیم. به پروژه یک آیتم از نوع LINQ to SQL با نام UserData.dbml اضافه می‌کنیم. به دیتابیس متصل شده و جدول User رو به محیط Design می‌کشیم. در ادامه برای شی کلاس SQLWorkflowInstanceStore هم از همین Connectionstring استفاده می‌کنیم. 

              برای ایجاد workflow مورد نظر، به دو Activity سفارشی احتیاج داریم که باید خودمان ایجاد نماییم. یک پوشه با نام Activities به پروژه اضافه می‌کنم تا کلاس‌های مورد نظر را آن‌جا ایجاد کنیم.

              1. یک Activity برای ایجاد User

              این Activity تعدادی پارامتر از نوع InArgument دارد که توسط آن‌ها یک Instance از کلاس User ایجاد می‌کند و در حقیقت آن را به دیتابیس می‌فرستد و دخیره می‌کند. Connectionstring را هم می‌شود توسط یک آرگومان ورودی دیگر مقدار دهی کرد. یک آرگومان خروجی هم برای این Activity در نظر می‌گیریم تا User ایجاد شده را برگردانیم. روی پوشه‌ی Activities کلیک راست می‌کنیم و Add - NewItem را انتخاب می‌کنیم. از لیست workflow‌ها Template مربوط به CodeActivity را انتخاب کرده و یک CodeActivity با نام CreateUser ایجاد می‌کنیم 

              محتویات این کلاس را هم مانند زیر کامل می‌کنیم 

              using System;
              using System.Collections.Generic;
              using System.Linq;
              using System.Text;
              using System.Activities;
              
              namespace PersistWF.UI.Activities
              {
              
                  public sealed class CreateUser : CodeActivity
                  {
              
              
                      public InArgument<string> Name { get; set; }
                      public InArgument<string> Email { get; set; }
                      public InArgument<string> Phone { get; set; }
                      public InArgument<string> ConnectionString { get; set; }
              
                      public OutArgument<User> User { get; set; }
              
                      protected override void Execute(CodeActivityContext context)
                      {
                          // ایجاد کاربر
                          User user = new User();
                          user.Email = Email.Get(context);
                          user.Name = Name.Get(context);
                          user.Phone = Phone.Get(context);
                          user.Status = "New";
                   user.WorkflowID = context.WorkflowInstanceId;
                          UserDataDataContext db = new UserDataDataContext(ConnectionString.Get(context));
                          db.Users.InsertOnSubmit(user);
                          db.SubmitChanges();
                          User.Set(context, user);
                      }
                  }
              }

              متد Execute، توسط مقادیری که به عنوان پارامتر دریافت شده، یک شی از کلاس User ایجاد می‌کند و به کمک DataContext آن‌را در دیتابیس دخیره کرده و در آخر User ذخیره شده را در اختیار پارامتر خروجی قرار می‌دهد.

              1. یک Activity برای انتظار دریافت تایید

              این Activity قرار است Workflow را Idle کند تا زمانیکه مدیر دستور تایید را با فراخوانی مجدد workflow از این همین قسمت صادر نماید.

              این Activity باید از NativeActivity مشتق شده و برای اینکه workflow را وادرا به معلق شدن کند کافی‌است خاصیت CanInduceIdle را با مقدار برگشتی true , override کنیم.

              مثل قسمت قبل یک CodeActivity ایجاد می‌کنیم. اینبار با نام WaitForAccept که محتویاتش را هم مانند زیر تغییر می‌دهیم. 

               using System;
              using System.Collections.Generic;
              using System.Linq;
              using System.Text;
              using System.Activities;
              using System.Workflow.ComponentModel;
              
              namespace PersistWF.UI.Activities
              {
              
                  public sealed class WaitForAccept<T> : NativeActivity<T>
                  {
                      public WaitForAccept()
                          :base()
                      {
              
                      }
                      public string BookmarkName { get; set; }
                      public OutArgument<T> Input { get; set; } 
              
                      protected override void Execute(NativeActivityContext context)
                      {
                          context.CreateBookmark(BookmarkName, new BookmarkCallback(this.Continue));
                      }
              
                      private void Continue(NativeActivityContext context, Bookmark bookmark, object value)
                      {
                          Input.Set(context, (T)value); 
                      }
                      protected override bool CanInduceIdle
                      {
                          get
                          {
                              return true;
                          }
                      }
                  }
              }
              این کلاس را generic نوشتم تا به جای User بشود هر پارامتر دیگه‌ای را به آن ارسال کرد. در واقع وقتی workflow به این Activity می‌رسد، Idle می‌شود. این activity  یک bookmark هم ایجاد می‌کند. ما وقتی workflow را با این bookmark فراخوانی کنیم؛ workflow از همینجا ادامه می‌یابد. فراخوانیbookmark می‌تواند همراه با وارد کردن یک  object باشد. متد Continue آن object را به آرگومان خروجی می‌دهد تا مسیر workflow را طی کند.
              ما User  هایی را که به این نقطه رسیدنْ نمایش می‌دهیم. مدیر اونها را دیده و با مقدار دهی فیلد AcceptedBy، آن User را از اینجا به workflow می‌فرستد و ما user وارد شده را در ادامه‌ی فرآیند Accept می‌کنیم.
               
              برای ایجاد workflow هم می‌توانید از designer استفاده کنید و هم می‌توانید کد مربوط به workflow را پیاده سازی کنید.

              برای پیاده سازی از طریق کد، یک کلاس با نام UserWF ایجاد می‌کنیم و محتویات workflow را مانند زیر پیاده سازی خواهیم کرد:

              using PersistWF.UI.Activities;
              using System;
              using System.Activities;
              using System.Activities.Statements;
              using System.Collections.Generic;
              using System.IO;
              using System.Linq;
              using System.Text;
              using System.Threading.Tasks;
              
              namespace PersistWF.UI
              {
                   public sealed class UserWF : Activity
                  {
                      public InArgument<string> Name { get; set; }
                      public InArgument<string> Email { get; set; }
                      public InArgument<string> Phone { get; set; }
                      public InArgument<string> ConnectionString { get; set; }
                      public InArgument<TextWriter> Writer { get; set; }
              
                      public UserWF()
                      {
                          Variable<User> User = new Variable<User> { Name = "User" };
                          this.Implementation = () => new Sequence
                          {
                              DisplayName = "EnterUser",
                              Variables = { User },
                              Activities = { 
                                   new CreateUser   //  1. ایجاد کاربر با ورود پارامتر‌های ورودی  
                                  {
                                      ConnectionString = new InArgument<string>(c=> ConnectionString.Get(c)),
                                      Email = new InArgument<string>(c=> Email.Get(c)),
                                      Name = new InArgument<string>(c=> Name.Get(c)),
                                      Phone = new InArgument<string>(c=> Phone.Get(c)),
                                      User = new OutArgument<User>(c=> User.Get(c))
                                  },
                                  new WriteLine // 2. لاگ مربوط به دخیره کاربر
                                  {
                                      TextWriter = new InArgument<TextWriter>(c=> Writer.Get(c)),
                                      Text = new InArgument<string>(c=> string.Format("User {0} Registered and waiting for Accept", Name.Get(c) ) )
                                  },
                                  new InvokeMethod 
                                   { 
                                       TargetType = typeof(ApplicationInterface),  // 3. برای به روزرسانی لیست کاربران ثبت شده در نمایش فرم
                                       MethodName = "NewUser", 
                                       Parameters = 
                                       { 
                                          new InArgument<User>(env => User.Get(env)) 
                                       } 
                                   }, 
                                   new WaitForAccept<User>  // 4. اینجا فرایند متوقف می‌شود و منتظر تایید مدیر می‌ماند
                                   {  
                                      BookmarkName = "GetAcceptes",
                                      Input = new OutArgument<User>(env => User.Get(env))
                                   },
                                   new WriteLine // 5. لاگ مربوط به تایید شدن کاربر
                                   {
                                       TextWriter = new InArgument<TextWriter>(c=> Writer.Get(c)),
                                       Text = new InArgument<string>(c=> string.Format("User {0} Accepter by {1}",Name.Get(c),User.Get(c).AcceptedBy))
                                   }
                              }
              
                          };
              
                      }
              
                  }
              }

              اگر بخوایم از Designer استفاده کنیم.  فرایندمان چیزی شبیه شکل زیر خواهد بود 

              به Application بر می‌گردیم تا آن را پیاده سازی کنیم. ابتدا به app.config که اتوماتیک ایجاد شده رفته تا اسم Connectionstring  رو به UserGenerator تغییر دهیم. محتویات درون app.config به شکل زیر است. 

              <?xml version="1.0" encoding="utf-8" ?>
              <configuration>
                  <configSections>
                  </configSections>
                  <connectionStrings>
                      <add name="UserGenerator"
                          connectionString="Data Source=.;Initial Catalog=PersistWF;Integrated Security=True"
                          providerName="System.Data.SqlClient" />
                  </connectionStrings>
                  <startup> 
                      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
                  </startup>
              </configuration>

              در کلاس AddRequest کد زیر را اضافه می‌کنم. برای نگهداری مقدار connectionstring 

              private string _connectionString = "";

              همچنین کد‌های زیر را به رویداد Load فرم اضافه می‌کنم تا مقدار ConnectionString را از Config بخوانم: 

              Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                          ConnectionStringsSection css = (ConnectionStringsSection)config.GetSection("connectionStrings");
                          _connectionString =  css.ConnectionStrings["UserGenerator"].ConnectionString;

              خط زیر را هم به کلاس AddRequest اضافه نمایید.  

              private InstanceStore _instanceStore;

              این ارجاعیه  به کلاس InstanceStore که برای Persist و Load کردن workflow از آن استفاده می‌کنیم و کد‌های زیر را هم به رویداد Load فرم اضافه می‌کنیم.  

              _instanceStore = new SqlWorkflowInstanceStore(_connectionString);
                          InstanceView view = _instanceStore.Execute(_instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
                          _instanceStore.DefaultInstanceOwner = view.InstanceOwner;

              InstanceStore یک کلاس abstract  می باشد که همه‌ی Provider‌های مربوط به persistence از آن مشتق می‌شوند. در این پروژه من از کلاس SqlWorkflowInstanceStore استفاده کردم تا workflow‌ها را در دیتابیسSQL Server ذخیره کنم.

              برای ایجاد یک Request مقادیر را از فرم دریافت کرده، یک User ایجاد می‌کنیم و آن را در فرآیند به جریان می‌اندازیم. این کار را در رویداد کلیک دکمه Register انجام می‌دهیم 

              private void brnRegister_Click(object sender, RoutedEventArgs e)
                      {
                          Dictionary<string, object> parameters = new Dictionary<string, object>();
                          parameters.Add("Name", txtName.Text);
                          parameters.Add("Phone", txtPhone.Text);
                          parameters.Add("Email", txtEmail.Text);
                          parameters.Add("ConnectionString", _connectionString);
                          parameters.Add("Writer", new ListBoxTextWriter(lstEvents));
                          WorkflowApplication i = new WorkflowApplication
                          (new UserWF(), parameters);
                          // Setup persistence 
                          i.InstanceStore = _instanceStore;
                          i.PersistableIdle = (waiea) => PersistableIdleAction.Unload;
                          i.Run(); 
                      }

              پارامتر‌های ورودی را از روی فرم مقدار دهی می‌کنیم. یک شی از کلاس WorkflowApplication ایجاد می‌کنیم. خاصیت InstanceStore آن را با Store ای که ایجاد کردیم مقدار دهی می‌کنیم. توسط رویداد PersistableIdle فرآیند رو مجبور می‌کنیم به Persist شدن و Unload شدن.

              و سپس فرایند را اجرا می‌کنم.

              اگر یادتان باشد، در فرآیند، از یک InvoceMethod استفاده کردیم. متد مورد نظر را هم در کلاس ApplicationInterface.cs ایجاد می‌کنیم. 

              public static void NewUser(User l)
                      {
                          if (_app != null)
                              _app.AddNewUser(l);
                      }

              همین طور که می‌بینید، یک متد هم در کلاس AddRequest ایجاد می‌شود؛ با این محتوا 

              public void AddNewUser(User l)
                      {
                          this.lstUsers.Dispatcher.BeginInvoke(new Action(() => this.lstUsers.Items.Add(l)));
                      }

              این متد فقط یک کاربر را به لیست کاربران اضافه می‌کند. این لیست همه کاربران را نشان می‌دهد. توسط رویداد SelectionChanged این کنترل، کاربر انتخاب شده را بررسی کرده در صورتی که کاربر جدید باشد، امکان تایید شدن را برایش فراهم می‌کنیم؛ که نمایش دکمه تایید است. 

              private void lstUsers_SelectionChanged(object sender, SelectionChangedEventArgs e)
                      {
                          if (lstUsers.SelectedIndex >= 0)
                          {
                              User l = (User)lstUsers.Items[lstUsers.SelectedIndex];
                              lblSelectedNotes.Visibility = Visibility.Visible;
                              if (l.Status == "New")
                              {
                                  lblAgent.Visibility = Visibility.Visible;
                                  txtAcceptedBy.Visibility = Visibility.Visible;
                                  btnAccept.Visibility = Visibility.Visible;
                              }
                              else
                              {
                                  lblAgent.Visibility = Visibility.Hidden;
                                  txtAcceptedBy.Visibility = Visibility.Hidden;
                                  btnAccept.Visibility = Visibility.Hidden;
                              }
                          }
                          else
                          {
                              lblSelectedNotes.Content = "";
                              lblSelectedNotes.Visibility = Visibility.Hidden;
                              lblAgent.Visibility = Visibility.Hidden;
                              txtAcceptedBy.Visibility = Visibility.Hidden;
                              btnAccept.Visibility = Visibility.Hidden;
                          } 
                      }

              و برای رویداد کلیک دکمه تایید کاربر : 

              private void btnAccept_Click(object sender, RoutedEventArgs e)
                      {
                          if (lstUsers.SelectedIndex >= 0) 
                           { 
                               User u = (User)lstUsers.Items[lstUsers.SelectedIndex]; 
                               Guid id = u.WorkflowID.Value;
                               UserDataDataContext dc = new UserDataDataContext(_connectionString); 
                               dc.Refresh(RefreshMode.OverwriteCurrentValues, dc.Users);
                               u = dc.Users.SingleOrDefault<User>(x => x.WorkflowID == id); 
                               if (u != null) 
                               { 
                                   u.AcceptedBy = txtAcceptedBy.Text; 
                                   u.Status = "Assigned"; 
                                   dc.SubmitChanges();
                                   // Clear the input 
                                   txtAcceptedBy.Text = "";
                               }
                               // Update the grid 
                               lstUsers.Items[lstUsers.SelectedIndex] = u;
                               lstUsers.Items.Refresh();
                               WorkflowApplication i = new WorkflowApplication(new UserWF());
                               i.InstanceStore = _instanceStore;
                               i.PersistableIdle = (waiea) => PersistableIdleAction.Unload;
                               i.Load(id);
                               try
                               {
                                   i.ResumeBookmark("GetAcceptes", u);
                               }
                               catch (Exception e2)
                               {
                                   AddEvent(e2.Message);
                               }
                           } 
                      }

              کاربر را انتخاب می‌کنم مقادیرش را تنظیم می‌کنیم. آن را دخیره کرده و workflow را از روی guid مربوط به آن که قبلا در فرآیند به Entity دادیم، Load می‌کنیم و همانطور که می‌بینید توسط متد ResumeBookmark فرآیند رو از جایی که می‌خواهیم ادامه می‌دهیم. البته می‌توان تایید کاربر را هم در خود فرآیند انجام داد و چون نوشتن Activity  مرتبط با آن تقریبا تکراری است با اجازه‌ی شما من اون رو ننوشتم و زحمتش با خودتونه.

              حالا فقط مانده‌است که همه کاربران را در ابتدای نمایش فرم از دیتابیس فراخوانی کنیم و در لیست نمایش دهیم:

              private void LoadExistingLeads()
                      {
                          UserDataDataContext dc = new UserDataDataContext(_connectionString);
                          dc.Refresh(RefreshMode.OverwriteCurrentValues, dc.Users);
                          IEnumerable<User> q = dc.Users;
                          foreach (User u in q)
                          {
                              AddNewUser(u);
                          }
                      }

              و فراخوانی این متد را به انتهای رویداد Load صفحه واگذار می‌کنیم.

              پروژه رو اجرا کرده و یک کاربر را اضافه می‌کنم. همانطور که می‌دانید این کاربر در فرآیند ایجاد و در دیتابیس ذخیره می‌شود

              برنامه را می‌بندم و دوباره اجرا می‌کنم. کاربر را انتخاب می‌کنم و یک نام برای admin انتخاب و آن را تایید می‌کنم. فرآیند را از bookmark مورد نظر اجرا کرده و به پایان می‌رسد. با بسته شدن برنامه، فرایند Idle و Unload می‌شود و ذخیره آن در sqlserver صورت می‌گیرد. 

              مطالب
              ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
              اگر یک پروژه‌ی خالی ASP.NET Core Web Application را شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
               .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
              تغییرات ساختاری ASP.NET Core 1.0، با نگارش‌های قبلی ASP.NET، بسیار قابل ملاحظه هستند:


              در اینجا نقش Solution همانند نگارش‌های قبلی ویژوال استودیو است: ظرفی است برای ساماندهی موارد مورد نیاز جهت تشکیل یک برنامه‌ی وب و شامل مواردی است مانند پروژه‌ها، تنظیمات آن‌ها و غیره. بنابراین هنوز در اینجا فایل sln. تشکیل می‌شود.


              نقش فایل global.json

              زمانیکه یک پروژه‌ی جدید ASP.NET Core 1.0 را آغاز می‌کنیم، ساختار پوشه‌های آن به صورت زیر هستند:


              در اینجا هنوز فایل sln. قابل مشاهده است. همچنین در اینجا فایل جدیدی به نام global.json نیز وجود دارد، با این محتوا:
              {
                "projects": [ "src", "test" ],
                "sdk": {
                  "version": "1.0.0-preview2-003121"
                }
              }
              شماره نگارش ذکر شده‌ی در اینجا را در قسمت قبل بررسی کردیم.
              خاصیت projects در اینجا به صورت یک آرایه تعریف شده‌است و بیانگر محل واقع شدن پوشه‌های اصلی پروژه‌ی جاری هستند. پوشه‌ی src یا source را در تصویر فوق مشاهده می‌کنید و محلی است که سورس‌های برنامه در آن قرار می‌گیرند. یک پوشه‌ی test نیز در اینجا ذکر شده‌است و اگر در حین ایجاد پروژه، گزینه‌ی ایجاد unit tests را هم انتخاب کرده باشید، این پوشه‌ی مخصوص نیز ایجاد خواهد شد.
              نکته‌ی مهم اینجا است، هرکدی که درون پوشه‌های ذکر شده‌ی در اینجا قرار نگیرد، قابلیت build را نخواهد داشت. به عبارتی این نسخه‌ی از ASP.NET پوشه‌ها را قسمتی از پروژه به حساب می‌آورد. در نگارش‌های قبلی ASP.NET، مداخل تعریف فایل‌های منتسب به هر پروژه، درون فایلی با پسوند csproj. قرار می‌گرفتند. معادل این فایل در اینجا اینبار پسوند xproj را دارد و اگر آن‌را با یک ادیتور متنی باز کنید، فاقد تعاریف مداخل فایل‌های پروژه است.
              در این نگارش جدید اگر فایلی را به پوشه‌ی src اضافه کنید یا حذف کنید، بلافاصله در solution explorer ظاهر و یا حذف خواهد شد.
              یک آزمایش: به صورت معمول از طریق windows explorer به پوشه‌ی src برنامه وارد شده و فایل پیش فرض Project_Readme.html را حذف کنید. سپس به solution explorer ویژوال استودیو دقت کنید. مشاهده خواهید کرد که این فایل، بلافاصله از آن حذف می‌شود. در ادامه به recycle bin ویندوز مراجعه کرده و این فایل حذف شده را restore کنید تا مجددا به پوشه‌ی src برنامه اضافه شود. اینبار نیز افزوده شدن خودکار و بلافاصله‌ی این فایل را می‌توان در solution explorer مشاهده کرد.
              بنابراین ساختار مدیریت فایل‌های این نگارش از ASP.NET در ویژوال استودیو، بسیار شبیه به ساختار مدیریت فایل‌های VSCode شده‌است که آن نیز بر اساس پوشه‌ها کار می‌کند و یک پوشه و تمام محتوای آن‌را به صورت پیش فرض به عنوان یک پروژه می‌شناسد. به همین جهت دیگر فایل csproj ایی در اینجا وجود ندارد و file system همان project system است.

              یک نکته: در اینجا مسیرهای مطلق را نیز می‌توان ذکر کرد:
                "projects": [ "src", "test", "c:\\sources\\Configuration\\src" ],
              اما در مورد هر مسیری که ذکر می‌شود، NET Core. باید بتواند یک سطح پایین‌تر از پوشه‌ی ذکر شده، فایل مهم project.json را پیدا کند؛ در غیراینصورت از آن صرفنظر خواهد شد. برای مثال برای مسیر نسبی src، مسیر src\MyProjectName\project.json را جستجو می‌کند و برای مسیر مطلق ذکر شده، این مسیر را c:\\sources\\Configuration\\src\\SomeName\\project.json


              کامپایل خودکار پروژه در ASP.NET Core 1.0

              علاوه بر تشخیص خودکار کم و زیاد شدن فایل‌های سیستمی پروژه، بدون نیاز به Add new item کردن آن‌ها در ویژوال استودیو، اگر سورس‌های برنامه را نیز تغییر دهید، فایل سورس جدیدی را اضافه کنید و یا فایل سورس موجودی را حذف کنید، کل پروژه به صورت خودکار کامپایل می‌شود و نیازی نیست این‌کار را به صورت دستی انجام دهید.
              یک آزمایش: برنامه را از طریق منوی debug و گزینه‌ی start without debugging اجرا کنید. اگر برنامه را در حالت معمول debug->start debugging اجرا کنید، حالت کامپایل خودکار را مشاهده نخواهید کرد. در اینجا (پس از start without debugging) یک چنین خروجی را مشاهده خواهید کرد:


              این خروجی حاصل اجرای کدهای درون فایل Startup.cs برنامه است:
               app.Run(async (context) =>
              {
                 await context.Response.WriteAsync("Hello World!");
              });
              اکنون در همین حال که برنامه در حال اجرا است و هنوز IIS Express خاتمه نیافته است، از طریق windows explorer، به پوشه‌ی src برنامه وارد شده و فایل Startup.cs را با notepad باز کنید. هدف این است که این فایل را در خارج از ویژوال استودیو ویرایش کنیم. اینبار سطر await دار را در notepad به نحو ذیل ویرایش کنید:
               await context.Response.WriteAsync("Hello DNT!");
              پس از آن اگر مرورگر را refresh کنید، بلافاصله خروجی جدید فوق را مشاهده خواهید کرد که بیانگر کامپایل خودکار پروژه در صورت تغییر فایل‌های آن است.
              این مساله قابلیت استفاده‌ی از ASP.NET Core را در سایر ادیتورهای موجود، مانند VSCode سهولت می‌بخشد.


              نقش فایل project.json

              فایل جدید project.json مهم‌ترین فایل تنظیمات یک پروژه‌ی ASP.NET Core است و مهم‌ترین قسمت آن، قسمت وابستگی‌های آن است:
              "dependencies": {
                "Microsoft.NETCore.App": {
                  "version": "1.0.0",
                  "type": "platform"
                },
                "Microsoft.AspNetCore.Diagnostics": "1.0.0",
                "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
                "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
                "Microsoft.Extensions.Logging.Console": "1.0.0"
              },
              همانطور که در قسمت قبل نیز عنوان شد، در این نگارش از دات نت، تمام وابستگی‌های پروژه از طریق نیوگت تامین می‌شوند و دیگر خبری از یک دات نت «بزرگ» که شامل تمام اجزای این مجموعه‌است نیست. این مساله توزیع برنامه را ساده‌تر کرده و همچنین امکان به روز رسانی سریع‌تر این اجزا را توسط تیم‌های مرتبط فراهم می‌کند؛ بدون اینکه نیازی باشد تا منتظر یک توزیع «بزرگ» دیگر ماند.
              در نگارش‌های قبلی ASP.NET، فایلی XML ایی به نام packages.config حاوی تعاریف مداخل بسته‌های نیوگت برنامه بود. این فایل در اینجا جزئی از محتوای فایل project.json در قسمت dependencies آن است.
              با قسمت وابستگی‌های این فایل، به دو طریق می‌توان کار کرد:
              الف) ویرایش مستقیم این فایل که به همراه intellisense نیز هست. با افزودن مداخل جدید به این فایل و ذخیره کردن آن، بلافاصله کار restore و دریافت و نصب آن‌ها آغاز می‌شود:


              ب) از طریق NuGet package manager
              روش دیگر کار با وابستگی‌ها، کلیک راست بر روی گره references و انتخاب گزینه‌ی manage nuget packages است:


              برای نمونه جهت نصب ASP.NET MVC 6 این مراحل باید طی شوند:


              ابتدا برگه‌ی browse را انتخاب کنید و سپس تیک مربوط به include prerelease را نیز انتخاب نمائید.
              البته بسته‌ی اصلی MVC در اینجا Microsoft.AspNetCore.Mvc نام دارد و نه MVC6.

              اینبار بسته‌هایی که restore می‌شوند، در مسیر اشتراکی C:\Users\user_name\.nuget\packages ذخیره خواهند شد.


              یک نکته‌ی مهم:
              قرار هست در نگارش‌های پس از RTM، فایل‌های project.json و xproj را جهت سازگاری با MSBuild، اندکی تغییر دهند (که این تغییرات به صورت خودکار توسط VS.NET انجام می‌شود). اطلاعات بیشتر


              انتخاب فریم ورک‌های مختلف در فایل project.json

              در قسمت قبل عنوان شد که ASP.NET Core را می‌توان هم برفراز NET Core. چندسکویی اجرا کرد و هم NET 4.6. مختص به ویندوز. این انتخاب‌ها در قسمت frameworks فایل project.json انجام می‌شوند:
              "frameworks": {
                "netcoreapp1.0": {
                  "imports": [
                    "dotnet5.6",
                    "portable-net45+win8"
                  ]
                }
              },
              در اینجا، netcoreapp1.0 به معنای برنامه‌‌ای است که برفراز NET Core. اجرا می‌شود. نام پیشین آن dnxcore50 بود (اگر مقالات قدیمی‌تر پیش از RTM را مطالعه کنید).
              در اینجا اگر علاقمند بودید که از دات نت کامل مخصوص ویندوز نیز استفاده کنید، می‌توانید آن‌را در لیست فریم ورک‌ها اضافه نمائید (در این مثال، دات نت کامل 4.5.2 نیز ذکر شده‌است):
               "frameworks": {
                  "netcoreapp1.0": {
                  },
                  "net452": {
                  }
                }
              لیست کامل این اسامی را در مستندات NET Starndard می‌توانید مطالعه کنید و خلاصه‌ی آن به این صورت است:
              - “netcoreapp1.0” برای معرفی و استفاده‌ی از NET Core 1.0. بکار می‌رود.
              - جهت معرفی فریم ورک‌های کامل و ویندوزی دات نت، اسامی “net45”, “net451”, “net452”, “net46”, “net461” مجاز هستند.
              -  “portable-net45+win8” برای معرفی پروفایل‌های PCL یا portable class libraries بکار می‌رود.
              - “dotnet5.6”, “dnxcore50” برای معرفی نگارش‌های پیش نمایش NET Core.، پیش از ارائه‌ی نگارش RTM استفاده می‌شوند.
              - “netstandard1.2”, “netstandard1.5” کار معرفی برنامه‌های NET Standard Platform. را انجام می‌دهند.

              بر این مبنا، dotnet5.6 ذکر شده‌ی در قسمت تنظیمات نگارش RTM، به این معنا است که قادر به استفاده‌ی از بسته‌های نیوگت و کتابخانه‌های تولید شده‌ی با نگارش‌های RC نیز خواهید بود (هرچند برنامه از netcoreapp1.0 استفاده می‌کند).

              یک مثال: قسمت فریم ورک‌های فایل project.json را به نحو ذیل جهت معرفی دات نت 4.6.1 تغییر دهید:
              "frameworks": {
                "netcoreapp1.0": {
                    "imports": [
                        "dotnet5.6",
                        "portable-net45+win8"
                    ]
                },
                "net461": {
                    "imports": [
                        "portable-net45+win8"
                    ],
                    "dependencies": {
                    }
                }
              },
              لیست وابستگی‌های خاص این فریم ورک در خاصیت dependencies آن قابل ذکر است.
              در این حالت پس از ذخیره‌ی فایل و شروع خودکار بازیابی وابستگی‌ها، با پیام خطای Package Microsoft.NETCore.App 1.0.0 is not compatible with net461 متوقف خواهید شد.
              برای رفع این مشکل باید وابستگی Microsoft.NETCore.App را حذف کنید، چون با net461 سازگاری ندارد
               "dependencies": {
               //"Microsoft.NETCore.App": {
               //  "version": "1.0.0",
               //  "type": "platform"
               //},

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


              فایل project.lock.json چیست؟


              ذیل فایل project.json، فایل دیگری به نام project.lock.json نیز وجود دارد. اگر به محتوای آن دقت کنید، این فایل حاوی لیست دقیق بسته‌های نیوگت مورد استفاده‌ی توسط برنامه است و الزاما با آن‌چیزی که در فایل project.json قید شده، یکی نیست. از این جهت که در فایل project.json، قید می‌شود netcoreapp1.0؛ ولی این netcoreapp1.0 دقیقا شامل چه بسته‌هایی است؟ لیست کامل آن‌ها را در این فایل می‌توانید مشاهده کنید.
              در ابتدای این فایل یک خاصیت locked نیز وجود دارد که مقدار پیش فرض آن false است. اگر به true تنظیم شود، در حین restore وابستگی‌های برنامه، تنها از نگارش‌های ذکر شده‌ی در این فایل استفاده می‌شود. از این جهت که در فایل project.json می‌توان شماره نگارش‌ها را با * نیز مشخص کرد؛ مثلا *.1.0.0


              پوشه‌ی جدید wwwroot و گره dependencies

              یکی از پوشه‌های جدیدی که در ساختار پروژه‌ی ASP.NET Core معرفی شده‌است، wwwroot نام دارد:


              از دیدگاه هاستینگ برنامه، این پوشه، پوشه‌ای است که در معرض دید عموم قرار می‌گیرد (وب روت). برای مثال فایل‌های ایستای اسکریپت، CSS، تصاویر و غیره باید در این پوشه قرار گیرند تا توسط دنیای خارج قابل دسترسی و استفاده شوند. بنابراین سورس کدهای برنامه خارج از این پوشه قرار می‌گیرند.
              گره dependencies که ذیل پوشه‌ی wwwroot قرار گرفته‌است، جهت مدیریت این وابستگی‌های سمت کلاینت برنامه است. در اینجا می‌توان از NPM و یا Bower برای دریافت و به روز رسانی وابستگی‌های اسکریپتی و شیوه‌نامه‌های برنامه کمک گرفت (علاوه بر نیوگت که بهتر است صرفا جهت دریافت وابستگی‌های دات نتی استفاده شود).
              یک مثال: فایل جدیدی را به نام bower.json به پروژه‌ی جاری با این محتوا اضافه کنید:
              {
                "name": "asp.net",
                "private": true,
                "dependencies": {
                  "bootstrap": "3.3.6",
                  "jquery": "2.2.0",
                  "jquery-validation": "1.14.0",
                  "jquery-validation-unobtrusive": "3.2.6"
                }
              }
              این نام‌ها استاندارد هستند. برای مثال اگر قصد استفاده‌ی از npm مربوط به node.js را داشته باشید، نام این فایل packag.json است (با ساختار خاص خودش) و هر دوی این‌ها را نیز می‌توانید با هم اضافه کنید و از این لحاظ محدودیتی وجود ندارد.
              پس از اضافه شدن فایل bower.json، بلافاصله کار restore بسته‌ها از اینترنت شروع می‌شود:


              و یا با کلیک راست بر روی گره dependencies، گزینه‌ی restore packages نیز وجود دارد.
              فایل‌های نهایی دریافت شده را در پوشه‌ی bower_components خارج از wwwroot می‌توانید مشاهده کنید.

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

              یک نکته: اگر خواستید نام پوشه‌ی wwwroot را تغییر دهید، فایل جدیدی را به نام hosting.json با این محتوا به پروژه اضافه کنید:
              {
                  "webroot":"AppWebRoot"
              }
              در اینجا AppWebRoot نام دلخواه پوشه‌ی جدیدی است که نیاز است به ریشه‌ی پروژه اضافه نمائید تا بجای wwwroot پیش فرض استفاده شود.


              نقطه‌ی آغازین برنامه کجاست؟

              اگر به فایل project.json دقت کنید، چنین تنظیمی در آن موجود است:
              "buildOptions": {
                "emitEntryPoint": true,
                "preserveCompilationContext": true
              },
              true بودن مقدار تولید entry point به استفاده‌ی از فایلی به نام Program.cs بر می‌گردد؛ با این محتوا:
              public static void Main(string[] args)
              {
                  var host = new WebHostBuilder()
                      .UseKestrel()
                      .UseContentRoot(Directory.GetCurrentDirectory())
                      .UseIISIntegration()
                      .UseStartup<Startup>()
                      .Build();
               
                  host.Run();
              }
              به این ترتیب، یک برنامه‌ی ASP.NET Core، دقیقا همانند سایر برنامه‌های NET Core. رفتار می‌کند و در اساس یک برنامه‌ی کنسول است.
               var outputKind = compilerOptions.EmitEntryPoint.GetValueOrDefault() ?
              OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary;
              این چند سطر، قسمتی از سورس کد ASP.NET Core 1.0 هستند. به این معنا که اگر مقدار خاصیت emitEntryPoint مساوی true بود، با این برنامه به شکل یک برنامه‌ی کنسول رفتار کن و در غیر اینصورت یک Dynamically Linked Library خواهد بود.
              در فایل Program.cs تنظیماتی را مشاهده می‌کنید، در مورد راه اندازی Kestler که وب سرور بسیار سریع و چندسکویی کار با برنامه‌های ASP.NET Core 1.0 است و قسمت مهم دیگر آن به استفاده‌ی از کلاس Startup بر می‌گردد (()<UseStartup<Startup). این کلاس را در فایل جدید Startup.cs می‌توانید ملاحظه کنید که کار تنظیمات آغازین برنامه را انجام می‌دهد. اگر پیشتر با OWIN، در نگارش‌های قبلی ASP.NET کار کرده باشید، قسمتی از این فایل برای شما آشنا است:
              public class Startup
              {
                  public void ConfigureServices(IServiceCollection services)
                  {
                  }
              
                  public void Configure(IApplicationBuilder app)
                  {
                      app.Run(async (context) =>
                      {
                          await context.Response.WriteAsync("Hello World!");
                      });
                  }
              }
              در اینجا امکان دسترسی به تنظیمات برنامه، معرفی سرویس‌ها، middleware‌ها و غیره وجود دارند که هرکدام، در قسمت‌های آتی به تفصیل بررسی خواهند شد.


              نقش فایل launchsetting.json


              محتویات پیش فرض این فایل برای قالب empty پروژه‌های ASP.NET Core 1.0 به صورت ذیل است:
              {
                "iisSettings": {
                  "windowsAuthentication": false,
                  "anonymousAuthentication": true,
                  "iisExpress": {
                    "applicationUrl": "http://localhost:7742/",
                    "sslPort": 0
                  }
                },
                "profiles": {
                  "IIS Express": {
                    "commandName": "IISExpress",
                    "launchBrowser": true,
                    "environmentVariables": {
                      "ASPNETCORE_ENVIRONMENT": "Development"
                    }
                  },
                  "Core1RtmEmptyTest": {
                    "commandName": "Project",
                    "launchBrowser": true,
                    "launchUrl": "http://localhost:5000",
                    "environmentVariables": {
                      "ASPNETCORE_ENVIRONMENT": "Development"
                    }
                  }
                }
              }
              همانطور که مشاهده می‌کنید، در اینجا تنظیمات IIS Express قابل تغییر هستند. برای مثال port پیش فرض برنامه 7742 تنظیم شده‌است.
              پروفایل‌هایی که در اینجا ذکر شده‌اند، در تنظیمات پروژه نیز قابل مشاهده هستند: (کلیک راست بر روی پروژه و مشاهده‌ی properties آن و یا دوبار کلیک بر روی گره properties)


              به علاوه امکان انتخاب این پروفایل‌ها در زمان آغاز برنامه نیز وجود دارند:


              نکته‌ی مهم تمام این موارد به قسمت environment variable قابل مشاهده‌ی در تصاویر فوق بر می‌گردد. این متغیر محیطی می‌تواند سه مقدار Development ، Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن، می‌توان پروفایل جدیدی را تشکیل داد. زمانیکه برنامه بر اساس پروفایل خاصی بارگذاری می‌شود، اینکه چه متغیر محیطی انتخاب شده‌است، در کلاس Startup قابل استخراج و بررسی بوده و بر این اساس می‌توان اقدامات خاصی را انجام داد. برای مثال تنظیمات خاصی را بارگذاری کرد و یا صفحات ویژه‌ای را فعال و غیرفعال کرد (این موارد را در قسمت‌های بعدی مرور می‌کنیم).
              همچنین در اینجا به ازای هر پروفایل مختلف می‌توان Url آغازین یا launch url و پورت آن‌را مجزا درنظر گرفت و یا از وب سرور دیگری استفاده کرد.
              راهنماهای پروژه‌ها
              جایگزینی حروف عربی با متد الحاقی ToPersianContent
              متد ToPersianContent جهت جایگزینی حروف (ی، ک، ه) عربی با معادل صحیح فارسی آنها توسعه یافته است. نحوه استفاده از آن بصورت زیر است:
              var persianContent = textBox1.ToPersianContent();
              همچنین در صورتی که نیاز به دریافت یک رشته اصلاح شده با اعداد فارسی باشد، می‌توان از امضای دوم این متد بصورت زیر استفاده کرد:
              var persianContent = textBox1.ToPersianContent(true);
              این متدها همراه با متد GetPersianNumbers در کلاس PersianExtensions پیاده سازی شده اند. وظیفه متد GetPersianNumbers برگرداندن رشته‌ی دریافتی پس از جایگزینی اعداد لاتین بصورت فارسی است که نحوه استفاده از آن را مشاهده می‌کنید:
              var numbers = "1392".GetPersianNumbers(); // return ۱۳۹۲


              مطالب
              نحوه صحیح تولید Url در ASP.NET MVC
              کار متد کمکی Url.Action ایجاد یک Url بر اساس تعاریف مسیریابی برنامه است. البته متد کمکی مشابهی نیز مانند Html.ActionLink در ASP.NET MVC وجود دارد که کار آن تولید یک لینک قابل کلیک است؛ اما ممکن است در حالتی خاص تنها نیاز به خود Url داشته باشیم و نه لینک قابل کلیک آن.

              الف) اگر از jQuery Ajax استفاده می‌کنید، حتما باید استفاده از Url.Action را لحاظ کنید

              برای نمونه اگر قسمتی از عملیات Ajaxایی برنامه شما به نحو زیر تعریف شده است :
              $.ajax({  
                         type: "POST",  
                          url: "/Home/EmployeeInfo",  
              ...
              ... غلط است!
              در این حالت برنامه شما تنها در زمانیکه در ریشه یک دومین قرار گرفته باشد کار خواهد کرد. اگر برنامه شما در مسیری مانند http://www.site.com/myNewApp نصب شود، کلیه فراخوانی‌های Ajax ایی آن دیگر کار نخواهند کرد چون مسیر url فوق به ریشه سایت اشاره می‌کند و نه مسیر جاری برنامه شما (در یک sub domain جدید).
              به همین جهت در یک چنین حالتی حتما باید به کمک Url.Action مسیر یک اکشن متد را معرفی کرد تا به صورت خودکار بر اساس محل قرارگیری برنامه و تعاریف مسیریابی آن، Url صحیحی تولید شود.

              @Url.Action("EmployeeInfo", "Home")

              ب) دریافت Url مطلق از یک Url.Action

              Urlهای تولید شده توسط Url.Action به صورت پیش فرض نسبی هستند (نسبت به محل نصب و قرارگیری برنامه تعریف می‌شوند). اگر نیاز به دریافت یک مسیر مطلق که با http برای مثال شروع می‌شود دارید، باید به نحو زیر عمل کرد:
              @Url.Action("About", "Home", null, "http")
              پارامتر چهارم تعریف شده، سبب خواهد شد لینک تولیدی از حالت نسبی خارج شود.


              ج) استفاده از Url.Action در یک کنترلر

              فرض کنید قصد تولید یک فید RSS را در کنترلری دارید. یکی از آیتم‌هایی که باید ارائه دهید، لینک به مطلب مورد نظر است. این لینک باید مطلق باشد همچنین در یک View هم قرار نداریم که به کمک @ بلافاصله به متد کمکی Url.Action دسترسی پیدا کنیم.
              در کنترلرها، وهله جاری کلاس به شیء Url و متد Action آن به نحو زیر دسترسی دارد و خروجی نهایی آن یک رشته است:

              var url = this.Url.Action(actionName: "Index",
                                                controllerName: "Post",
                                                protocol: "http",
                                                routeValues: new { id = item.Id });
              بنابراین در کنترلرها نیز Urlها را دستی تولید نکنید و اجازه دهید بر اساس پارامترهای زیادی که در پشت صحنه Url.Action لحاظ می‌شود، Url صحیحی برای شما تولید گردد.


              د) استفاده از Url.Action در کلاس‌های کمکی برنامه خارج از یک کنترلر

              فرض کنید قصد تهیه یک Html Helper سفارشی را به کمک کدنویسی در یک کلاس مجزا دارید. در اینجا نیز نباید Urlها را دستی تولید کرد. در Html Helperهای سفارشی نیز می‌توان به کمک متد  UrlHelper.GenerateUrl، به همان امکانات Url.Action دسترسی یافت:

              public static class Extensions  
                  {  
                      public static string MyLink(this HtmlHelper html, ...)  
                      {  
                          string url = UrlHelper.GenerateUrl(null, "actionName", "controllerName",
                                                              null,
                                                              html.RouteCollection,
                                                              html.ViewContext.RequestContext,
                                                              includeImplicitMvcValues: true);
              //...
              
               
              مطالب
              آشنایی با NuGet - قسمت دوم

              قسمت قبل از دید یک مصرف کننده بود؛ این قسمت جهت توسعه‌ دهنده‌ها تهیه شده است. کسانی که قصد دارند تا بسته‌های NuGet ایی از کارشان تهیه کنند. مراحل اینکار به شرح زیر است:

              الف) برای این منظور نیاز است تا برنامه‌ی‌ خط فرمان NuGet.exe معرفی شده در قسمت قبل را ابتدا دریافت کنید : (+)

              ب) برای بسته نرم افزاری خود یک پوشه جدید درست کنید. سپس فرمان nuget.exe spec را در این پوشه صادر نمائید. بلافاصله فایلی به نام Package.nuspec تشکیل خواهد شد:
              D:\Prog\1389\CodePlex\slpdatepicker\SlPDatePickerNuGet>NuGet.exe spec
              Created 'Package.nuspec' successfully.

              فایل Package.nuspec، یک فایل XML ساده است. آن‌را با یک ادیتور متنی باز کرده و تغییرات لازم را اعمال نمائید. برای مثال من جهت پروژه Silverlight 4 Persian DatePicker ، محتویات آن‌را به صورت زیر تغییر داده‌ام:

              <?xml version="1.0"?>
              <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
              <metadata>
              <id>Silverlight.4.Persian.DatePicker</id>
              <version>1.0</version>
              <authors>Vahid Nasiri</authors>
              <owners>Vahid Nasiri</owners>
              <licenseUrl>http://slpdatepicker.codeplex.com/license</licenseUrl>
              <projectUrl>http://slpdatepicker.codeplex.com/</projectUrl>
              <iconUrl>https://slpdatepicker.svn.codeplex.com/svn/SilverlightPersianDatePicker/Views/Images/date.png</iconUrl>
              <requireLicenseAcceptance>false</requireLicenseAcceptance>
              <description>Silverlight 4 Persian DatePicker Control</description>
              <tags>Silverlight WPF Persian DatePicker</tags>
              </metadata>
              <files>
              <file src="..\SilverlightPersianDatePicker\Bin\Release\*.dll" target="lib" />
              <file src="..\SilverlightPersianDatePicker\Bin\Release\*.pdb" target="lib" />
              <file src="..\SilverlightPersianDatePicker\Bin\Release\*.xml" target="lib" />
              </files>
              </package>

              همانطور که ملاحظه می‌کنید یک سری اطلاعات عمومی از پروژه مورد نظر درخواست شده است؛ برای مثال آدرس آیکن آن چیست یا کجا می‌توان آن‌را یافت؟ مجوز استفاده از آن چیست و مواردی از این دست. به کمک تگ files هم فایل‌های کتابخانه در اینجا لحاظ شده‌اند. فایل آیکن معرفی شده باید در اندازه‌ی 32*32 و با فرمت png باشد. باید دقت داشت که در سایت nuget.org ، بسته شما بر اساس id ذکر شده معرفی خواهد شد و آدرسی بر این اساس تشکیل می‌گردد. بنابراین از فاصله یا موارد مشکل ساز در این بین استفاده نکنید.

              در مورد نحوه‌ی ایجاد قدم به قدم یک پروژه جدید در سایت کدپلکس می‌توان به این مطلب مراجعه نمود: (+)

              ج) اکنون نوبت به تهیه بسته نهایی می‌رسد. برای این منظور دستور زیر را در خط فرمان صادر کنید:
              NuGet.exe pack Package.nuspec
              پس از چند لحظه فایل Silverlight.4.Persian.DatePicker.1.0.nupkg جهت ارائه عمومی تولید خواهد شد.

              د) قبل از اینکه این فایل نهایی را در سایت nuget.org آپلود کنیم، می‌توان مشخصات آن‌را به صورت محلی نیز یکبار مرور کرد. برای این منظور در VS.NET به منوی Tools گزینه‌ی Options مراجعه کرده و در قسمت package manager ، آدرس پوشه بسته مورد نظر را وارد کنید. برای مثال:



              اکنون اگر کنسول پاورشل توضیح داده شده در قسمت قبل را باز نمائید، منبع جدید اضافه شده مشخص است یا می‌توان توسط دستور ذیل از آن کوئری گرفت:
              get-package -remote -filter silverlight



              و یا اگر همانند توضیحات قبل به صفحه‌ی دیالوگ add library package reference‌ مراجعه کنیم، مشخصات کامل بسته به همراه منبع محلی باید قابل مشاهده باشند:



              ه) پس از بررسی محلی بسته مورد نظر، اکنون نوبت به ارائه عمومی آن می‌باشد. برای این منظور ابتدا باید در سایت nuget.org ثبت نام کرد : (+). اگر آدرس ایمیل شما را نپذیرفت، از مرورگر IE استفاده کنید!
              پس از ثبت نام تنها کافی است به قسمت contribute سایت مراجعه کرده و فایل بسته نهایی را در آنجا آپلود کرد. به این صورت بسته نهایی در سایت پدیدار خواهد شد :‌(+)
              همچنین بلافاصله در قسمت گالری آنلاین صفحه add library package reference نیز قابل دسترسی خواهد بود.


              در آینده جهت توزیع به روز رسانی‌های جدید، همین مراحل باید تکرار شوند. البته در نظر داشته باشید که version ذکر شده در فایل Package.nuspec را باید حتما تغییر داد تا بسته‌ها از یکدیگر متمایز شوند. امکان اتوماسیون این توزیع نیز وجود دارد. همان فایل nuget.exe ، امکان ارسال بسته نهایی را به سایت nuget.org نیز دارد:
              nuget push name.nupkg key
              در اینجا key مخصوص به خود را می‌توان در صفحه‌ی http://nuget.org/Contribute/MyAccount مشاهده و استفاده نمود.

              اگر علاقمند به مشاهده جزئیات بیشتری از این پروسه هستید، می‌توان به سایت رسمی آن مراجعه کرد: (+)

              مطالب
              شروع به کار با AngularJS 2.0 و TypeScript - قسمت نهم - مسیریابی
              یک برنامه، از صفحات و Viewهای مختلفی تشکیل می‌شود و Routing یا مسیریابی، امکان ناوبری بین این Viewها را میسر می‌کند. یک برنامه‌ی AngularJS 2.0، یک برنامه‌ی تک صفحه‌ای وب است. به این معنا که تمام Viewهای برنامه، در یک صفحه نمایش داده می‌شوند؛ که معمولا همان index.html سایت است. هدف از سیستم مسیریابی، مدیریت نحوه‌ی نمایش و قرارگیری این Viewها، درون تک صفحه‌ی برنامه است.


              برپایی تنظیمات اولیه‌ی سیستم مسیریابی در AngularJS 2.0

              برای کار با سیستم مسیریابی AngularJS 2.0، ابتدا باید اسکریپت‌های آن به صفحه اضافه شوند. در ادامه المان پایه‌ای تعریف شده و سپس باید سرویس پروایدر مسیریابی را رجیستر کرد. جزئیات این موارد را در ادامه بررسی می‌کنیم:

              الف) سرویس مسیریابی، جزئی از angular2/core نیست. به همین جهت مدخل اسکریپت متناظر با آن باید به صفحه‌ی اصلی سایت اضافه شود که این مورد، در قسمت اول بررسی پیشنیازهای نصب AngularJS 2.0 صورت گرفته‌است:
               <!-- Required for routing -->
              <script src="~/node_modules/angular2/bundles/router.dev.js"></script>
              این تعریف در فایل Views\Shared\_Layout.cshtml (و یا index.html) پروژه‌ی جاری موجود است.

              ب) افزودن المان base به ابتدای صفحه:
               <!DOCTYPE html>
              <html>
              <head>
                  <base href="/">
              بلافاصله پس از تگ head، المان base اضافه می‌شود. این المان به سیستم مسیریابی اعلام می‌کند که چگونه آدرس‌های خود را تشکیل دهد. به صورت پیش فرض، AngularJS 2.0 از آدرس‌هایی با فرمت HTML5 استفاده می‌کند. در این حالت دیگر نیازی به ذکر # برای مشخص سازی مسیریابی‌های محلی نیست.
              از آنجائیکه فایل index.html در ریشه‌ی سایت قرار گرفته‌است، مقدار آغازین href آن به / تنظیم شده‌است.

              ج) شبیه به حالت ثبت پروایدر HTTP در قسمت قبل، برای ثبت پروایدر مسیریابی نیز به فایل App\app.component.ts مراجعه می‌کنیم:
              //same as before ...
              import { ROUTER_PROVIDERS } from 'angular2/router';
               
              //same as before ... 
              
              @Component({
              //same as before …
                  providers: [
                      ProductService,
                      HTTP_PROVIDERS,
                      ROUTER_PROVIDERS
                  ]
              })
              //same as before ...
              در اینجا سرویس ROUTER_PROVIDERS به خاصیت providers اضافه شده‌است و همچنین import متناظر با آن نیز به ابتدای صفحه اضافه گردیده‌است.
              علت ختم شدن نام این سرویس‌ها به PROVIDERS این است که این تعاریف، امکان استفاده‌ی از چندین سرویس زیر مجموعه‌ی آن‌ها را فراهم می‌کنند و صرفا یک سرویس نیستند.


              ساخت کامپوننت نمایش جزئیات محصولات

              در ادامه می‌خواهیم جزئیات هر محصول را با کلیک بر روی نام آن در لیست محصولات، در آدرسی دیگر به صورتی مجزا مشاهده کنیم. به همین منظور به پوشه‌ی products برنامه مراجعه کرده و دو فایل جدید product-detail.component.ts و product-detail.component.html را ایجاد کنید؛ با این محتوا:
              الف) محتوای فایل product-detail.component.html
              <div class='panel panel-primary'>
                  <div class='panel-heading'>
                      {{pageTitle}}
                  </div>
              </div>
              ب) محتوای فایل product-detail.component.ts
              import { Component } from 'angular2/core';
              
              @Component({
                  templateUrl: 'app/products/product-detail.component.html'
              })
              export class ProductDetailComponent  {
                  pageTitle: string = 'Product Detail'; 
              }
              در اینجا یک کامپوننت جدید را ایجاد کرده‌ایم که در قالب آن، مقدار خاصیت pageTitle با روش interpolation در آن درج شده‌است. کلاس ProductDetailComponent، قالب خود را از طریق مقدار دهی آدرس آن در خاصیت templateUrl متادیتای خود، پیدا می‌کند.
              اگر دقت کنید، این کامپوننت ویژه دارای خاصیت selector نیست. ذکر selector تنها زمانی اجباری است که بخواهیم این کامپوننت را داخل کامپوننتی دیگر قرار دهیم. در اینجا قصد داریم این کامپوننت را به صورت یک View جدید، توسط سیستم مسیریابی نمایش دهیم و نه به صورت جزئی از یک کامپوننت دیگر.


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

              مسیریابی در AngularJS 2.0، مبتنی بر کامپوننت‌ها است. به همین جهت، ابتدای کار مسیریابی، مشخص سازی تعدادی از کامپوننت‌ها هستند که قرار است به عنوان مقصد سیستم راهبری (navigation) مورد استفاده قرار گیرند و به ازای هر کدام، یک مسیریابی و Route جدید را تعریف می‌کنیم. تعریف هر Route جدید شامل انتساب نامی به آن، تعیین مسیر مدنظر و مشخص سازی کامپوننت مرتبط است:
               { path: '/products', name: 'Products', component: ProductListComponent },
              برای مثال جهت تعریف Route کامپوننت لیست محصولات، نام آن‌را Products، مسیر آن‌را products/ و در آخر کامپوننت آن‌را به نام کلاس متناظر با آن، تنظیم می‌کنیم.
              این تنظیمات به عنوان یک متادیتای جدید دیگر به کلاس AppComponent، در فایل app.component.ts اضافه می‌شوند:
              //same as before …
              import { ROUTER_PROVIDERS, RouteConfig } from 'angular2/router';
               
              //same as before …
               
              @Component({
                  //same as before …
              })
              @RouteConfig([
                  { path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true },
                  { path: '/products', name: 'Products', component: ProductListComponent }
              ])
              export class AppComponent {
                  pageTitle: string = "DNT AngularJS 2.0 APP";
              }
              در اینجا decorator جدیدی به نام RouteConfig به کلاس AppComponent اضافه شده‌است و همچنین importهای متناظری نیز در ابتدای این فایل تعریف شده‌اند.
              همانطور که ملاحظه می‌کنید، یک کلاس می‌تواند بیش از یک decorator داشته باشد.
              ()RouteConfig@ را به کامپوننتی الصاق می‌کنیم که قصد میزبانی مسیریابی را دارد (Host component). این مزین کننده، آرایه‌ای از اشیاء را قبول می‌کند و هر شیء آن دارای خواصی مانند مسیر، نام و کامپوننت است. باید دقت داشت که نام هر مسیریابی تعریف شده باید pascal case باشد. در غیراینصورت مسیریاب ممکن است این نام را با path اشتباه کند.  
              همچنین امکان تعریف خاصیت دیگری به نام useAsDefault نیز در اینجا میسر است. از آن جهت تعریف مسیریابی پیش فرض سیستم، در اولین بار نمایش آن، استفاده می‌شود.

              در اینجا نام کامپوننت، رشته‌ای ذکر نمی‌شود و دقیقا اشاره دارد به نام کلاس متناظر. بنابراین هر نام کلاسی که در اینجا اضافه می‌شود، باید به همراه import ماژول آن نیز در ابتدای فایل جاری باشد. به همین جهت اگر تنظیمات فوق را اضافه کنید، ذیل کلمه‌ی WelcomeComponent یک خط قرمز مبتنی بر عدم تعریف آن کشیده می‌شود. برای تعریف آن، پوشه‌ی جدیدی را به ریشه‌ی سایت به نام home اضافه کنید و به آن دو فایل ذیل را اضافه نمائید:
              الف) محتوای فایل welcome.component.ts
              import { Component } from 'angular2/core';
               
              @Component({
                  templateUrl: 'app/home/welcome.component.html'
              })
              export class WelcomeComponent {
                  public pageTitle: string = "Welcome";
              }
              ب) محتوای فایل welcome.component.html
              <div class="panel panel-primary">
                  <div class="panel-heading">
                      {{pageTitle}}
                  </div>
                  <div class="panel-body">
                      <h3 class="text-center">Default page</h3>
                  </div>
              </div>
              کار این کامپوننت، نمایش صفحه‌ی آغازین برنامه است؛ بر اساس تنظیم useAsDefault: true مسیریابی‌های تعریف شده‌.
              پس از تعریف این کامپوننت، اکنون باید import ماژول آن‌را به ابتدای فایل app.component.ts اضافه کنیم، تا مشکل عدم شناسایی نام کلاس WelcomeComponent برطرف شود:
               import { WelcomeComponent } from './home/welcome.component';


              فعال سازی مسیریابی‌های تعریف شده

              روش‌های مختلفی برای دسترسی به اجزای یک برنامه وجود دارند؛ برای مثال کلیک بر روی یک لینک، دکمه و یا تصویر و سپس فعال سازی مسیریابی متناظر با آن. همچنین کاربر می‌تواند آدرس صفحه‌ای را مستقیما در نوار آدرس‌های مرورگر وارد کند. به علاوه امکان کلیک بر روی دکمه‌های back و forward مرورگر نیز همواره وجود دارند. تنظیمات مسیریابی‌های انجام شده، دو مورد آخر را به صورت خودکار مدیریت می‌کنند. در اینجا تنها باید مدیریت اولین حالت ذکر شده را با اتصال مسیریابی‌ها به اعمال کاربران، انجام داد.
              به همین جهت منویی را به بالای صفحه‌ی برنامه اضافه می‌کنیم. برای این منظور، فایل app.component.ts را گشوده و خاصیت template کامپوننت AppComponent را به نحو ذیل تغییر می‌دهیم:
              @Component({
                  //same as before …
                  template: `
                   <div>
                      <nav class='navbar navbar-default'>
                          <div class='container-fluid'>
                              <a class='navbar-brand'>{{pageTitle}}</a>
                              <ul class='nav navbar-nav'>
                                  <li><a [routerLink]="['Welcome']">Home</a></li>
                                  <li><a [routerLink]="['Products']">Product List</a></li>
                              </ul>
                          </div>
                      </nav>
                      <div class='container'>
                          <router-outlet></router-outlet>
                      </div>
                   </div>
                  `,
                  //same as before …
              })
              در اینجا یک navigation bar بوت استرپ 3، جهت تعریف منوی بالای صفحه اضافه شده‌است.
              سپس جهت تعریف لینک‌های هر آیتم، از یک دایرکتیو توکار AngularJS 2.0 به نام routerLink استفاده می‌کنیم. هر routerLink به یکی از آیتم‌های تنظیم شده‌ی در RouteConfig بایند می‌شود. بنابراین نام‌هایی که در اینجا قید شده‌اند، دقیقا نام‌هایی هستند که در خاصیت name هر کدام از اشیاء تشکیل دهنده‌ی RouteConfig، تعریف و مقدار دهی گردید‌ه‌اند.
              اکنون اگر کاربر بر روی یکی از لینک‌های Home و یا Product List کلیک کند، مسیریابی متناظر با آن فعال می‌شود (بر اساس این نام، در لیست عناصر RouteConfig جستجویی صورت گرفته و عنصر معادلی بازگشت داده می‌شود) و سپس View آن کامپوننت نمایش داده خواهد شد.
              تا اینجا دایرکتیو جدید routerLink به قالب کامپوننت اضافه شد‌ه‌است؛ اما AngularJS 2.0 نمی‌داند که باید آن‌را از کجا دریافت کند. به همین جهت ابتدا import آن‌را (ROUTER_DIRECTIVES) به ابتدای ماژول جاری اضافه خواهیم کرد:
               import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
              و سپس خاصیت دایرکتیوهای کامپوننت ریشه‌ی سایت را نیز با آن مقدار دهی خواهیم کرد:
               directives: [ROUTER_DIRECTIVES],
              علت جمع بود نام این دایرکتیو این است که routerLink استفاده شده، یکی از اعضای مجموعه‌ی دایرکتیوهای مسیریابی توکار AngularJS 2.0 است.

              تا اینجا اگر دقت کرده باشید، کامپوننت نمایش لیست محصولات را از کامپوننت ریشه‌ی سایت حذف کرده‌ایم و بجای آن منوی بالای سایت را نمایش می‌دهیم که توسط آن می‌توان به صفحه‌ی آغازین و یا صفحه‌ی نمایش لیست محصولات، رسید. به همین جهت خاصیت directives دیگر شامل ذکر کلاس کامپوننت لیست محصولات نیست.

              در انتهای قالب کامپوننت ریشه‌ی سایت، یک دایرکتیو جدید به نام router-outlet نیز تعریف شده‌است. وقتی یک کامپوننت فعال می‌شود، نیاز است View مرتبط با آن نیز نمایش داده شود. دایرکتیو router-outlet محل نمایش این View را مشخص می‌کند.

              اکنون اگر برنامه را اجرا کنیم، به این شکل خواهیم رسید:


              اگر دقت کنید، آدرس بالای صفحه، در اولین بار نمایش آن به http://localhost:2222/welcome تنظیم شده و این مقدار دهی بر اساس خاصیت useAsDefault تنظیمات مسیریابی سایت انجام شده‌است (نمایش welcome به عنوان صفحه‌ی اصلی و پیش فرض).
              همچنین با کلیک بر روی لینک لیست محصولات، کامپوننت آن فعال شده و نمایش داده می‌شود. محل قرارگیری این کامپوننت‌ها، دقیقا در محل قرارگیری دایرکتیو router-outlet است.


              ارسال پارامترها به سیستم مسیریابی

              در ابتدا بحث، مقدمات کامپوننت نمایش جزئیات یک محصول انتخابی را تهیه کردیم. برای فعال سازی این کامپوننت و مسیریابی آن، نیاز است بتوان پارامتری را به سیستم مسیریابی ارسال کرد. برای مثال با انتخاب آدرس product/5، جزئیات محصول با ID مساوی 5 نمایش داده شود.
              برای این منظور:
              الف) اولین قدم، تعریف مسیریابی آن است. به همین جهت به فایل app.component.ts مراجعه و دو تغییر ذیل را به آن اعمال کنید:
              //same as before …
              
              import { ProductDetailComponent } from './products/product-detail.component';
               
              @Component({
                  //same as before …
              })
              @RouteConfig([
                  //same as before …
                  { path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent }
              ])
              //same as before …
              ابتدا مسیریابی جدیدی به نام ProductDetail اضافه شده‌است و سپس ماژول دربرگیرنده‌ی کلاس کامپوننت آن نیز import شده‌است.
              تفاوت این مسیریابی با نمونه‌های قبلی در تعریف id:/ است. پس از ذکر :/، نام یک متغیر عنوان می‌شود و اگر نیاز به چندین متغیر بود، همین الگو را تکرار خواهیم کرد.

              ب) سپس نحوه‌ی فعال سازی این مسیریابی را توسط تعریف لینکی جدید، معرفی می‌کنیم. بنابراین فایل قالب product-list.component.html را گشوده و سپس بجای نمایش عنوان محصول:
               <td>{{ product.productName }}</td>
              لینک به جزئیات آن‌را نمایش می‌دهیم:
              <td>
                  <a [routerLink]="['ProductDetail', {id: product.productId}]">
                      {{product.productName}}
                  </a>
              </td>
              نحوه‌ی تعریف این لینک، با لینک‌هایی که در منوی بالای سایت اضافه کردیم، یکی است؛ با این تفاوت که اکنون پارامتر دومی را به قسمت یافتن نام این Route، جهت مشخص سازی روش مقدار دهی متغیر id، تعریف کرده‌ایم. در اینجا id هر لینک از productId بایند شده تامین می‌شود.
              اکنون که از دایرکتیو جدید routerLink در این قالب استفاده شده‌است، نیاز است تعریف دایرکتیو آن‌را به متادیتای کلاس کامپوننت لیست محصولات نیز اضافه کنیم تا AngularJS 2.0 بداند آن‌را از کجا باید تامین کند:
              import { Component, OnInit } from 'angular2/core';
              import { ROUTER_DIRECTIVES } from 'angular2/router';
              //same as before …
               
              @Component({
                  //same as before …
                  directives: [StarComponent, ROUTER_DIRECTIVES]
              })

              در ادامه اگر برنامه را اجرا کنید، عنوان‌های محصولات، به آدرس نمایش جزئیات آن‌ها لینک شده‌اند:


              ج) در آخر زمانیکه View نمایش جزئیات محصول فعال می‌شود، نیاز است این id را از url جاری دریافت کند. به همین جهت فایل product-detail.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
              import { Component } from 'angular2/core';
              import { RouteParams } from 'angular2/router';
               
              @Component({
                  templateUrl: 'app/products/product-detail.component.html'
              })
              export class ProductDetailComponent {
                  pageTitle: string = 'Product Detail';
               
                  constructor(private _routeParams: RouteParams) {
                      let id = +this._routeParams.get('id');
                      this.pageTitle += `: ${id}`;
                  } 
              }
              با نحوه‌ی تزریق وابستگی‌ها در قسمت هفتم آشنا شدیم. در اینجا سرویس توکار RouteParams به سازنده‌ی کلاس تزریق شده‌‌است. با استفاده از آن می‌توان به id ارسالی از طریق url دسترسی یافت. در اینجا پارامتری که به متد get ارسال می‌شود، باید با نام پارامتری که در تنظیمات آغازین مسیریابی سیستم تعریف گردید، تطابق داشته باشد.
              در این حالت، id دریافتی، به متغیر pageTitle اضافه شده و در قالب مربوطه به صورت خودکار نمایش داده می‌شود.

              تا اینجا اگر برنامه را اجرا کنید، صفحه‌ی نمایش جزئیات یک محصول، با کلیک بر روی عناوین آن‌ها به صورت زیر نمایش داده می‌شود:



              افزودن دکمه‌ی back با کدنویسی

              اکنون برای بازگشت مجدد به لیست محصولات، می‌توان از دکمه‌ی back مرورگر استفاده کرد، اما امکان طراحی این دکمه در قالب‌ها نیز پیش بینی شده‌است.
              برای این منظور قالب product-detail.component.html را به نحو ذیل بازنویسی می‌کنیم:
              <div class='panel panel-primary'>
                  <div class='panel-heading'>
                      {{pageTitle}}
                  </div>
                  <div class='panel-footer'>
                      <a class='btn btn-default' (click)='onBack()' style='width:80px'>
                          <i class='glyphicon glyphicon-chevron-left'></i> Back
                      </a>
                  </div>    
              </div>
              در اینجا دکمه‌ی بازگشت به صفحه‌ی قبلی اضافه شده‌است که به متد onBack در کلاس مرتبط با این قالب، متصل است.

              سپس کدهای product-detail.component.ts را به صورت ذیل تکمیل خواهیم کرد:
              import { Component } from 'angular2/core';
              import { RouteParams, Router } from 'angular2/router';
               
              @Component({
                  templateUrl: 'app/products/product-detail.component.html'
              })
              export class ProductDetailComponent {
                  pageTitle: string = 'Product Detail';
               
                  constructor(private _routeParams: RouteParams, private _router: Router) {
                      let id = +this._routeParams.get('id');
                      this.pageTitle += `: ${id}`;
                  }
               
                  onBack(): void {
                      this._router.navigate(['Products']);
                  }
              }
              در اینجا سرویس جدیدی به نام Router در سازنده‌ی کلاس تزریق شده‌است. این سرویس امکان فراخوانی متدهایی مانند navigate را جهت حرکت به مسیریابی مشخصی، میسر می‌کند. پارامتری که به این متد ارسال می‌شود، دقیقا معادل همان پارامتری است که به دایرکتیو routerLink ارسال می‌گردد و در اینجا صرفا نام یک مسیریابی مشخص شده‌است؛ بدون ذکر پارامتری.


              رفع تداخل مسیریابی‌های ASP.NET MVC با مسیریابی‌های AngularJS 2.0

              در طی بحث جاری عنوان شد که اگر کاربر مسیر http://localhost:2222/product/2 را جایی ثبت کرده یا bookmark کند، پس از فراخوانی مستقیم آن در نوار آدرس‌های مرورگر، بلافاصله به این آدرس هدایت خواهد شد. این مورد صحیح است اگر از index.html بجای بکارگیری ASP.NET MVC، جهت هاست برنامه استفاده شود. اگر چنین آدرسی را در یک برنامه‌ی ASP.NET MVC فراخوانی کنیم، ابتدا به دنبال کنترلری به نام product می‌گردد (ابتدا وارد موتور ASP.NET MVC می‌شود) و چون این کنترلر در سمت سرور تعریف نشده‌است، پیام 404 و یا یافت نشد را مشاهده خواهید کرد و فرصت به اجرای برنامه‌ی AngularJS نخواهد رسید.
              برای حل این مشکل نیاز است یک route جدید را به نام catch all، در انتهای مسیریابی‌های فعلی اضافه کنید؛ تا سایر درخواست‌های رسیده را به صفحه‌ی نمایش برنامه‌ی AngularJS هدایت کند:
              public class RouteConfig
              {
                  public static void RegisterRoutes(RouteCollection routes)
                  {
                      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
               
                      routes.MapRoute(
                          name: "Default",
                          url: "{controller}/{action}/{id}",
                          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                          constraints: new { controller = "Home" } // for catch all to work, Home|About|SomeName
                      );
               
                      // Route override to work with Angularjs and HTML5 routing
                      routes.MapRoute(
                          name: "NotFound",
                          url: "{*catchall}",
                          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
                      );
                  }
              }


              کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part9.zip


              خلاصه‌ی بحث

              حین ایجاد کامپوننت‌ها باید به نحوه‌ی نمایش آن‌ها نیز فکر کرد. اگر کامپوننتی قرار است داخل یک کامپوننت دیگر نمایش یابد، باید دارای selector باشد. یک چنین کامپوننتی نیاز به تعریف مسیریابی ندارد. برای کامپوننت‌هایی که به عنوان یک View مستقل طراحی می‌شوند و قرار است در یک صفحه‌ی مجزا نمایش داده شوند، نیازی به تعریف selector نیست؛ اما باید برای آن‌ها مسیریابی‌های ویژه‌ای را تعریف کرد. همچنین نیاز است مدیریت اعمال کاربران را جهت فعال سازی آن‌ها نیز مدنظر داشت. برای استفاده از امکانات مسیریابی توکار AngularJS 2.0 نیاز است اسکریپت آن‌را به صفحه‌ی اصلی اضافه کرد. سپس باید المان base را جهت نحوه‌ی تشکیل آدرس‌های مسیریابی، به صفحه اضافه کرد. در ادامه کار ثبت ROUTER_PROVIDERS در بالاترین سطح سلسه مراتب کامپوننت‌های سایت انجام می‌شود. با استفاده از RouteConfig کار تنظیمات ابتدایی مسیریابی صورت خواهد گرفت. این decorator به کامپوننتی که قرار است کار میزبانی مسیریابی را انجام دهد، متصل می‌شود. پس از تعریف مسیریابی‌ها با ذکر یک نام منحصربفرد، مسیری که باید توسط کاربر وارد شود و نام کامپوننت مدنظر، با استفاده از دایرکتیو routerLink کار تعریف این آدرس‌ها، در رابط کاربری برنامه انجام می‌شود. این دایرکتیو جدید، جزئی از مجموعه‌ی ROUTER_DIRECTIVES است که باید به لیست دایرکتیوهای کامپوننت ریشه‌های سایت و هر کامپوننتی که از routeLink استفاده می‌کند، اضافه شود. برای بایند این دایرکتیو به مسیریابی‌های تعریف شده، سمت راست این اتصال باید به آرایه‌ای از مقادیر مقدار دهی شود که اولین عنصر آن، نام یکی از عناصر مسیریابی تعریف شده‌ی در RouteConfig است. از دومین عنصر آن برای مقدار دهی پارامترهای ارسالی به سیستم مسیریابی استفاده می‌شود. کار دایرکتیو router-outlet، مشخص سازی محل نمایش یک View است که عموما در قالب میزبان مسیریابی قرار می‌گیرد. برای تعیین پارامترهای مسیریابی، از الگوی paramName:/ استفاده می‌شود. برای دسترسی به این مقادیر در یک کامپوننت، می‌توان از سرویس RouteParams استفاده کرد. برای فعال سازی یک مسیریابی با کدنویسی، از سرویس Router و متد navigate آن کمک می‌گیریم.
              مطالب
              آموزش فایرباگ - #5 - HTML Panel
              در این سری از مقالات آموزش FireBug ، به صورت ترتیبی پیش رفتیم و ابتدا توضیحات تقریبا مفصلی در مورد پنل Console دادیم.
              اکنون به پرکاربردترین بخش آن ، یعنی پنل HTML می‌رسیم.

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

              نگاهی به کدهای ارسال شده به مرورگر در آدرس Google.com و نحوه‌ی نمایش آن در فایرباگ را ملاحظه بفرمایید.
              ( کدهای ارسال شده سمت چپ - نمایش در فایرباگ سمت راست )


              این پنل به سه بخش اصلی تقسیم می‌شود :
              • بخش اصلی یا NodeView که محتوای صفحه را بصورت درختی و مرتب و رنگی نمایش می‌دهد.
              •  Panel Toolbar که در بالای پنل قرار دارد.
              •  Side Panels که شامل پنل‌های Style , Computed , Layout , DOM می‌شود.
                که به ترتیب برای نمایش و ویرایش استایل‌ها ، مشاهده استایل‌های محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView است.

              در این مقاله با دو بخش NodeView و Panel Toolbar ، و در مقاله‌ی بعد با پنل‌های سمت راست یعنی Side Panels آشنا می‌شویم.

              ویرایش HTML

              هر تگ HTML از یک سری Attribute تشکیل می‌شود که در فایرباگ نام ویژگی بصورت آبی تیره و مقدار آن با رنگ قرمز مشخص شده است. برای ویرایش هریک از آن‌ها کافیست برویش کلیک کنید تا آن مقدار در یک باکس ویرایش به نمایش دربیاید.
              با ویرایش مقدار ، این تغییر در لحظه بروی صفحه اعمال می‌شود.


              برای اضافه کردن یک Attribute جدید به تگ هم بروی تگ مورد نظر راست کلیک می‌کنید و سپس گزینه‌ی New Attribute را انتخاب می‌کنید. ابتدا نام ویژگی ، یک Enter ، سپس مقدار را وارد می‌کنید. با زدن کلیدهای Enter متوالی ، می‌توانید به وارد کردن ویژگی‌ها ادامه دهید.
              برای ویرایش کردن یک تگ و تگ‌های فرزندش ، بروی تگ موردنظر کلیک کنید تا به حالت انتخاب دربیاید ، سپس بروی دکمه‌ی Edit در بالای پنل کلیک کنید. در نهایت بعد از انجام ویرایش ، مجددا بروی دکمه‌ی Edit کلیک کنید.


               Node View
              NodeView نام بخش اصلی پنل HTML است که محتویات صفحه را بصورت مرتب شده و درختی نمایش می‌دهد.
              تگ (Node) هایی که دارای فرزند می‌باشند ، یک علامت + یا - در کنارشان وجود دارد که با کلیک بروی آن می‌توانید آن تگ را باز/بسته کنید. همچنین این کار با کلید‌های + و - یا Right Arrow و Left Arrow از روی کیبورد هم قابل انجام است.
              برای باز کردن یک لینک در این قسمت هم از ترکیب Ctrl و کلیک موس کمک بگیرید.
              در نهایت زمانی که موس را بروی یک تگ قرار می‌دهید ، محیطی که توسط آن تگ در صفحه اشغال شده است بصورت رنگی مشخص می‌شود.
              که رنگ زرد به معنی محیط margin ، رنگ آبی تیره به معنی محیط padding و رنگ آبی روشن هم به معنی محیط محتوا است.

               Panel Toolbar
              این قسمت در بالای پنل HTML قرار دارد که گزینه‌های زیر را دارا می‌باشد:



              •  Break On Mutate
                این دکمه امکان توقف کد JavaScript ای که سعی در ویرایش محتوای صفحه را دارد ، می‌دهد.
                زمانی که FireBug تشخیص دهد که کدی سعی در ویرایش محتوا دارد ، شما را به خط مورد نظر از کد ، در پنل Script منتقل می‌کند.

              • Edit
                این دکمه برای ویرایش مستقیم محتوای یک تگ بکار می‌رود
                نکته‌ی جالب در ویرایش محتوا در فایرباگ این است که تغییرات در لحظه اعمال می‌شوند و نیاز به عمل بروزرسانیِ جداگانه نیست. برای مثال در همین قسمت Edit ، با هر ویرایش محتوا ، تغییرات در لحظه اعمال می‌شوند.

              •  Element Path
                زمانی که یک تگ را در FireBug انتخاب می‌کنید ، لیستی از تگ‌ها که از تگ جاری شروع و به تگ ریشه ختم می‌شود ، نمایش داده می‌شود که با کلیک بروی هرکدام ، همان به عنوان تگ فعلی یا انتخاب شده تعیین می‌شود.
                نتیجه‌ی عملیاتی که بروی این تگ‌های انجام می‌دهید (حرکت موس و راست کلیک کردن) معادل همان عملیات بروی تگ‌های نمایش داده شده در قسمت اصلی (NodeView) است.



              Options Menu

              هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب HTML هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
              این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر می‌شود.


              • Show Full Text
                در صورت فعال بودن ، متون بصورت کامل نمایش داده می‌شوند ، در غیراینصورت بصورت خلاصه شده نمایش داده می‌شوند.

              • Show White Space
                در صورت فعال بودن ، فضاهای خالی ، کرکترهای خط جدید و ... را نمایش می‌دهد.


              • Show Comments
                در صورت فعال بودن ، کامنت‌ها را هم نمایش می‌دهد


              • سه گزینه ی Show Entities As Symbols ، Show Entities As Names و Show Entities As Unicode ، نوع نمایش کرکترهای ویژه را تعیین می‌کنند.

              • Highlight Changes
                در صورت فعال بودن ، تگ تغییر یافته توسط JavaScript (یا تگ والدی که قابل مشاهده باشد) Highlight می‌شود

              • Expand Changes
                در صورت فعال بودن ، زمان تغییر دادن یک تگ توسط JavaScript ، والدهای آن تگ باز (Expand) می‌شوند

              • Scroll Changes Into View
                در صورت فعال بودن این گزینه ، هنگام تغییر یک تگ در صفحه توسط JavaScript ، قسمت NodeView به قسمت تغییر بافته حرکت می‌کند.
                (فعال بودن این گزینه هنگام بررسی سایت هایی که اسلایدر یا سیستم هایی مشابه دارند ، باعث میشه که نتوانید بروی قسمت‌های سایت تمرکز کنید و مدام اسکرول به قسمت تغییرات منتقل می‌شود.)

              • Shade Box Model
                در صورت فعال بودن ، فضای margin , padding و content را به شکلی که در بالا گفته شد نمایش می‌دهد ، در غیر اینصورت فقط یک خط دور تگ نشان می‌دهد.

              • Show Quick Info Box
                در صورت فعال بودن ، یک پاپ‌آپ به همراه اطلاعات مختصری از تگ در صفحه نمایش می‌دهد.




              Context Menu
              اگر بروی یک تگ راست کلیک کنید ، یک منو نمایش داده می‌شود ، در این قسمت با گزینه‌های این منو آشنا می‌شویم.

              • Copy HTML
                خود تگ و محتوایش را بصورت HTML در حافظه کپی می‌کند.

              • Copy innerHTML
                محتوای تگ را در حافظه کپی می‌کند.

              • Copy XPath
                آدرس XPath تگ را در حافظه کپی می‌کند.

              • Copy CSS Path
                CSS Selector تگ را در حافظه کپی می‌کند.

              • Log Events
                رویدادهای تگ را در پنل Console ثبت می‌کند. (کلیک موس ، فشردن کلید ، ...)
                برای لغو لاگ کردن ، مجددا بروی تگ راست کلیک کرده و این گزینه را از حالت انتخاب خارج کنید.

              • Scroll Into View
                صفحه را به جایی که تگ قابل نمایش است منتقل می‌کند.

              • New Attribute...
                یک attribute جدید به تگ اضافه می‌کند.
                برای لغو عملیات ، دکمه‌ی Esc را بزنید.

              • Edit Attribute "<attribute name>"...
                اگر بروی یک attribute راست کلیک کرده باشید ، این گزینه و گزینه‌ی بعدی را مشاهده خواهید کرد.
                معادل کلیک بروی نام attribute است ، نام را به حالت ویرایش درمی آورد.

              • Delete Attribute "<attribute name>"
                attribute را حذف می‌کند.

              • Edit HTML...
                تگ را به حالت ویرایش می‌برد.
                معادل انتخاب تگ ، زدن کلید Edit است.

              • Delete Element
                تگ را حذف می‌کند.
                راه دیگر حذف یک تگ ، انتخاب تگ و فشردن کلید Del از کیبورد است.

              • Expand/Contract All
                تگ و Childهایش را باز/بسته می‌کند. (بجز تگ های <script> , <style> , <link>)
                می‌توان با ترکیب کلیدShift + * هم این کار را انجام داد که در این حالت تگ‌های فوق هم شامل باز/بسته شدن می‌شوند.

              • Break On Attribute Change
                مانع اجرای کد JavaScript ای که attribute تگ را تغییر می‌دهد می‌شود و فایرباگ به خطی که باعث این عمل شده است در پنل Script منتقل می‌شود.
                به عبارتی دیگر یک Break Point در خط JavaScript ای که باعث ویرایش attribute می‌شود قرار می‌دهد.

              • Break On Child Addition or Removal
                مشابه توضیح قبل ، Break Point را در خطی که باعث اضافه/حذف شدن تگِ Child شده است قرار می‌دهد.

              • Break On Element Removal
                مشابه توضیح قبل ، Break Point را در خطی که باعث حذف شدن تگ شده است قرار می‌دهد.

              • Inspect in DOM Tab
                تگ فعلی را در پنل DOM ، برای بررسی باز می‌کند.


              در قسمت بعدی با پنل‌های سمت راست (Side Panels) آشنا می‌شویم.