مطالب
INPC استاندارد با بهره گیری از صفت CallerMemberName
یکی از Attribute‌های بسیار کاربردی که در سی شارپ 5 اضافه شد CallerMemberNameAttribute بود. این صفت به یک متد اجازه میدهد که از فراخواننده‌ی خود مطلع شود. این صفت را می‌توان بر روی یک پارامتر انتخابی که مقدار پیش‌فرضی دارد اعمال نمود.

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

private void A ( [CallerMemberName] string callerName = "") 
{
  Console.WriteLine("Caller is " + callerName);
}

private static void B()
{
        // let's call A
        A();
}
در کد فوق، متد A به راحتی می‌تواند بفهمد چه کسی آن را فراخوانی کرده است. از جمله کاربردهای این صفت در ردیابی و خطایابی است.

ولی یک استفاده‌ی بسیار کاربردی از این صفت، در پیاده سازی رابط INotifyPropertyChanged می‌باشد.

معمولا هنگام پیاده سازی INotifyPropertyChanged کدی شبیه به این را می‌نویسیم:

    public class PersonViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                this.name = value;
                OnPropertyChanged("Name");
            }
        }
    }

یعنی در Setter معمولا نام ویژگی ای را که تغییر کرده است، به متد OnPropertyChanged می‌فرستیم تا اطلاع رسانی‌های لازم انجام پذیرد. تا اینجای کار همه چیز خوب و آرام است. اما به محضی که کد شما کمی طولانی شود و شما به دلایلی نیاز به Refactor کردن کد و احیانا تغییر نام ویژگی‌ها را پیدا کنید، آن موقع مسائل جدیدی بروز پیدا می‌کند.

برای مثال فرض کنید پس از نوشتن کلاس PersonViewModel تصمیم می‌گیرد نام ویژگی Name را به FirstName تغییر دهید؛ چرا که می‌خواهید اجزای نام یک شخص را به صورت مجزا نگهداری و پردازش کنید. پس احتمالا با زدن کلید F2 روی فیلد name آن را به firstName و ویژگی Name را به FirstName تغییر نام می‌دهید. همانند کد زیر:

private string firstName;
public string FirstName
{
            get { return firstName; }
            set
            {
                this.firstName = value;
                OnPropertyChanged("Name");
            }
}

برنامه را کامپایل کرده و در کمال تعجب می‌بینید که بخشی از برنامه درست رفتار نمی‌کند و تغییراتی که در نام کوچک شخص توسط کاربر ایجاد می‌شود به درستی بروزرسانی نمی‌شوند. علت ساده است: ما کد را به صورت اتوماتیک Refactor کرده ایم و گزینه‌ی Include String را در حین Refactor، در حالت پیشفرض غیرفعال رها کرده‌ایم. پس جای تعجبی ندارد که در هر جای کد که رشته‌ای به نام "Name" با ماهیت نام شخص داشته ایم، دست نخورده باقی مانده است. در واقع در کد تغییر یافته، هنگام تغییر FirstName، ما به سیستم گزارش می‌کنیم که ویژگی Name (که اصلا وجود ندارد) تغییر یافته است و این یعنی خطا.

حال احتمال بروز این خطا را در ViewModel هایی با ده‌ها ویژگی و ترکیب‌های مختلف در نظر بگیرید. پس کاملا محتمل است و برای خیلی از دوستان این اتفاق رخ داده است.

و اما راه حل چیست؟ به کارگیری صفت CallerMemberName

بهتر است که یک کلاس انتزاعی برای تمام ViewModel‌های خود داشته باشیم و پیاده سازی جدید INPC را در درون آن قرار دهیم تا براحتی VM‌های ما از آن مشتق شوند:

public abstract class ViewModelBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            OnPropertyChangedExplicit(propertyName);
        }

        protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
        {
            var memberExpression = (MemberExpression)projection.Body;
            OnPropertyChangedExplicit(memberExpression.Member.Name);
        }

        void OnPropertyChangedExplicit(string propertyName)
        {
            this.CheckPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;

            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #region Check property name

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void CheckPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
                throw new Exception(String.Format("Could not find property \"{0}\"", propertyName));
        }

        #endregion // Check property name
}

در این کلاس، ما پارامتر propertyName را از متد OnPropertyChanged، توسط صفت CallerMemberName حاشیه نویسی کرده‌ایم. این کار باعث می‌شود در Setter‌های ویژگی‌ها، به راحتی بدون نوشتن نام ویژگی، عملیات اطلاع رسانی تغییرات را انجام دهیم. بدین صورت که کافیست متد OnPropertyChanged بدون هیچ آرگومانی در Setter فراخوانی شود و صفت CallerMemberName به صورت اتوماتیک نام ویژگی ای که فراخوانی از درون آن انجام شده است را درون پارامتر propertyName قرار می‌دهد.

پس کلاس PersonViewModel را به صورت زیر می‌توانیم اصلاح و تکمیل کنیم:

public class PersonViewModel : ViewModelBase
{
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                this.firstName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                this.lastName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        public string FullName
        {
            get { return string.Format("{0} {1}", FirstName, LastName); }
        }
}
همانطور که می‌بینید متد OnPropertyChanged بدون آرگومان فراخوانی میشود. اکنون اگر شما اقدام به Refactor کردن کد خود بکنید دیگر نگرانی از بابت تغییر نکردن رشته‌ها و کامنت‌ها نخواهید داشت و مطمئن هستید، نام ویژگی هر چیزی که باشد، به صورت خودکار به متد ارسال خواهد شد.

کلاس ViewModelBase یک پیاده سازی دیگر از OnPropetyChanged هم دارد که به شما اجازه می‌دهد با استفاده دستورات لامبدا، OnPropertyChanged را برای هر یک از اعضای دلخواه کلاس نیز فراخوانی کنید. همانطور که در مثال فوق می‌بینید، تغییرات نام خانوادگی در نام کامل شخص نیز اثرگذار است. در نتیجه به وسیله‌ی یک Func به راحتی بیان می‌کنیم که FullName هم تغییر کرده است و اطلاع رسانی برای آن نیز باید صورت پذیرد.

برای استفاده از صفت CallerMemberName باید دات نت هدف خود را 4.5 یا 4.6 قرار دهید.

ارجاع:
Raise INPC witout string name
بازخوردهای پروژه‌ها
افزودن جدول به فوتر سفارشی
با سلام.
چگونه می‌شود یک جدول را به فوتر سفارشی اضافه کرد؟ متد مورد نظر را پیدا نکردم.
با تشکر.
مطالب
Markup Extensions در XAML
Markup Extension‌ها برای مواردی استفاده می‌شوند که قرار است مقداری غیر از یک مقدار ثابت و یک نوع قابل شناسایی در XAML برای یک value تنظیم شود. تمام مواردی در XAML که درون {} قرا می‌گیرند همان Markup Extension‌ها هستند. مانند Binding و یا StaticResoiurces.
علاوه بر Markup Extension‌های از پیش تعریف شده در XAML، می‌توان Markup Extension‌های شخصی را نیز تولید کرد. در واقع به زبان ساده‌تر Markup Extension برای تولید ساده‌ی داده‌های پیچیده در XAML استفاده می‌شوند.

لازم به ذکر است کهMarkup Extension ‌ها می‌توانند به دو صورت Attribute Usage ،درون  {} :
 "{Binding path=something,Mode=TwoWay}”
و یا Property Element Usage (همانند سایر Element هایWPF) درون <> استفاده شوند:
 <Binding Path="Something" Mode="TwoWay"></Binding>
برای تعریف یک Markup Extension، یک کلاس ایجاد می‌کنیم که از Markup Extensions ارث بری می‌کند. این کلاس یک Abstract Method به نام  ProvideValue دارد که باید پیاده سازی  شود. این متد مقدار خصوصیتی که Markup Extensions را فراخوانی کرده به صورت یک Object بر می‌گرداند که یکبار در زمان Load برای خصوصیت مربوطه‌اش تنظیم می‌شود.
 public abstract Object ProvideValue(IServiceProvider serviceProvider)
همانطورکه ملاحظه می‌کنید ProvideValue یک پارامتر IServiceProvider دارد که ازطریق آن می‌توان به IProvideValueTarget دسترسی داشت. ازاین Interface برای گرفتن اطلاعات کنترل(TargetObject) و خصوصیتی (TargetProperty) که فراخوانی را انجام داده در صورت لزوم استفاده می‌شود.
var target = serviceProviderGetService(typeof(IProvideValueTarget))as IProvideValueTarget;
var host = targetTargetObject as FrameworkElement;
Markup Extension‌ها می‌توانند پارامتر‌های ورودی داشته باشند:
public class ValueExtension : MarkupExtension
{
  public ValueExtension () { }
  public ValueExtension (object value1)
  {
    Value1 = value1;
  }
   public object Value1 { get; set; }
   public override object ProvideValue(IServiceProvider serviceProvider)
   {
     return Value1;
   }
}
و برای استفاده در فایل Xaml:
 <TextBox  Text="{app:ValueExtension test}" ></TextBox>
و یا می‌توان خصوصیت هایی ایجاد کرد و  از آنها برای ارسال مقادیر به آن استفاده کرد:
  <TextBox  Text="{app:ValueExtension Value1=test}" ></TextBox>
تا اینجا موارد کلی برای تعریف و استفاده از Markup Extensions گفته شد. در ادامه یک مثال کاربردی می‌آوریم. برای مثال در نظر بگیرید که نیاز دارید DataType مربوط به یک DataTemplate را برابر یک کلاس Generic قرار بدهید:
public class EntityBase
{
   public int Id{get;set}
}

public class MyGenericClass<TType> where TType : EntityBase
{
   public int Id{get;set}
   public string Test{  get;set; }

In XAML:

<DataTemplate DataType="{app:GenericType ؟}">
برای انجام این کار یک Markup Extensions به صورت زیر ایجاد می‌کنیم که Type را به عنوان ورودی گرفته و یک نمونه از کلاس Generic ایجاد می‌کند:
public class GenericType : MarkupExtension
{
  private readonly Type _of;
  public GenericType(Type of)
  {
     _of = of;
  }
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
      return typeof(MyGenericClass<>)MakeGenericType(_of);
 }
}
و برای استفاده از آن یک نمونه از MarkupExtension ایجاد شده ایجاد کرده و نوع Generic را برای آن ارسال می‌کنیم:
 <DataTemplate DataType="{app:GenericType app:EntityBase}">
این یک مثال ساده از استفاده از Markup Extensions است. هنگام کار با WPF می‌توان استفاده‌های زیادی از این مفهوم داشت، برای مثال زمانی که نیاز است ItemsSource یک  Combobox  از Description‌های یک Enum پر شود می‌توان به راحتی با نوشتن یک Markup Extensions ساده این عمل و کارهای مشابه زیادی را انجام داد.  
بازخوردهای دوره
استفاده از StructureMap به عنوان یک IoC Container

سلام؛ برای اعمال توکار دات نت چه کار باید کرد. مثلا زمانی که CustomRole یا CustomMemberShip داریم و متد سازنده ما کانتکس و کلاس‌های دیتالایر را به عنوان پارامتر ورودی میگیره ، این گونه موارد و نمی‌تونم تزریق وابستگی‌ها را انجام داد. من اشتباه می‌کنم یا راه دیگه ای داره؟

نظرات مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
- این مورد انتخاب شخصی است بیشتر. اگر می‌خواهید یکی را با VSCode کار کنید و دیگری را با VS کامل، شاید جدا باشند بهتر باشد. یا اینکه هر دو را هم می‌توان با VSCode کار کرد (اگر NET Core. کار می‌کنید).
- بستگی به توزیع نهایی برنامه دارد. آیا قرار است برنامه‌ی Angular بر روی یک پورت دیگر و یا یک دومین دیگر به صورت مجزایی ارائه شود؟ بله. در این‌حالت باید CORS فعال شود (یک مثال فعالسازی آن). در غیراینصورت اگر فایل‌های نهایی Angular در پوشه‌ی wwwroot برنامه‌ی وب کپی می‌شوند، نیازی به تغییر اضافه‌تری نیست.
نظرات مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور
یک نکته‌ی تکمیلی
اگر نکات مطلب «استفاده از مسیرهای مطلق در حین import ماژول‌ها در برنامه‌های مبتنی بر TypeScript» را به پروژه‌ی جاری اعمال کنیم، به این تغییرات خواهیم رسید.
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 2 - ثبت اولین سرویس
یک پروژه‌ی ASP.NET Core را با قرار دادن نسخه‌ی NET Core. بر روی 3.1 و با استفاده از قالب Model View Controller ایجاد کنید. در اینجا نام پروژه را AspNetCoreDependencyInjection گذاشته‌ام. حالا در  پوشه‌ی Models، فایلی را با نام HomeViewModel.cs با محتویاتی به صورت زیر اضافه کنید:
public class HomeViewModel
{
     public string Id { get; set; }
     public string Message { get; set; }
     public DateTime DateTime { get; set; }
}

اکنون به پوشه‌ی Views بروید و فایل Index.cshtml را به این صورت تغییر دهید:

@model AspNetCoreDependencyInjection.Models.HomeViewModel
@{
ViewData["Title"] = "Home";
}

<div>
<div>
<div>
<p>
<b>Id : </b><span>@Model.Id</span> <br />
<b>Date And Time : </b><span> @Model.DateTime </span> <br/>
<b>Message : </b><span>@Model.Message</span>
</p>
</div>
</div>
</div>
و فایل MessageServiceA.cs را به پروژه اضافه کنید:
using AspNetCoreDependencyInjection.Services;

namespace AspNetCoreDependencyInjection.ServicesImplentaions
{
    public class MessageServiceAA 
    {
        public string Message()
        {
            return "A message from MessageServiceAA";
        }
    }
}
و همچنین فایل GuidHelper.cs را نیز اضافه می‌کنیم:
namespace AspNetCoreDependencyInjection.Helpers
{
    public class GuidProvider
    {
        private readonly Guid _serviceGuid;

        public GuidProvider()
        {
            _serviceGuid = Guid.NewGuid();
        }

        public Guid GetNewGuid() => Guid.NewGuid();

        public string GetGuidAsFormatedString(string prefix = "") => getFormatedGuid(_serviceGuid, prefix);

        private string getFormatedGuid(Guid guid, string prefix = "")
        {
            var guidString = guid.GetHashCode().ToString("x");
            if (string.IsNullOrEmpty(prefix) == false)
                guidString = new StringBuilder($"{prefix}-").Append(guidString).ToString();
            return guidString;
        }
    }
}

حالا درون کنترل HomeController، این تغییرات را انجام می‌دهیم:

private readonly ILogger<HomeController> _logger;
private readonly MessageServiceAA _messageService;
private readonly GuidProvider _ guidProvider;

public HomeController(ILogger<HomeController> logger)
{
            _logger = logger;
            _messageService = new MessageServiceAA();
            _guidProvider = new GuidProvider();
}

public IActionResult Index()
{
            var model = new HomeViewModel()
            {
                Id = _ guidProvider.GetGuidAsFormatedString(),
                Message = _messageService.Message(),
                DateTime = DateTime.Now,
            };
            return View(model);
}

همانطور که می‌بینید، در کد بالا، کنترلر HomeController، به دو شیء از کلاس‌ها و یا سرویس‌های GuidProvider و MessageServiceAA به صورت مستقیم وابسته شده‌است و با هر تغییری در هر کدام از این سرویسها، باید دوباره کامپایل شود. علاوه بر این اگر بخواهیم پیاده سازی‌های مختلفی را برای هر کدام از این موارد، ارائه دهیم، به مشکل بر می‌خوریم. خب بیاید تغییراتی را در کد بالا بدهیم تا مشکلات ذکر شده را حل کنیم.

برای این منظور پوشه‌ای را به نام Services می‌سازیم و اینترفیسی را به نام IMessageBrokerA ایجاد می‌کنیم و سپس کاری می‌کنیم که MessageServiceAA از این اینترفیس ارث بری کند:

namespace AspNetCoreDependencyInjection.Services
{
    public interface IMessageServiceA
    {
        string Message();
    }
}

و حالا می‌خواهیم با استفاده از تزریق وابستگی، وابستگی کنترلر HomeController را از کلاس MessageBrokerAA لغو کرده و آن را به اینترفیس IMessageBrokerA (انتزاع) وابسته کنیم. در اینجا ما از تکنیک تزریق درون سازنده یا Constructor Injection استفاده می‌کنیم.


تزریق درون سازنده

در این تکنیک، ما لیستی از وابستگی‌های مورد نیاز را به عنوان پارامترهای ورودی سازنده‌ی کلاس، تعریف می‌کنیم:
private readonly ILogger<HomeController> _logger;
private readonly IMessageServiceA _messageService;
private readonly GuidProvider _guidHelper;
public HomeController(ILogger<HomeController> logger , IMessageServiceA messageService)
{
        _logger = logger;
        _messageService = messageService;
        _messageService = new MessageServiceAA();
        _guidHelper = new GuidProvider();
}
و حالا اگر برنامه را اجرا کنیم، با خطایی روبه رو می‌شویم که در آن می‌گوید امکان واکشی (Resolve) سرویس‌های مورد نظر وجود ندارد. این خطا به دلیل ثبت نشدن اینترفیس IMessageServiceA و پیاده سازی آن، درون Microsoft Dependency Injection Container است   DI Container‌ها معمولا باید در زمان شروع برنامه، پیکربندی و مقدار دهی شودند، تا در ادامه‌ی چرخه‌ی حیات برنامه، بتوانند سرویس‌ها و اشیاء مورد نیاز را به کلاس‌هایی که نیاز دارند، واکشی و تزریق کنند. اولین مرحله از کار با DI Container‌ها، ثبت کردن سرویس‌ها درون آنهاست.
در ASP.NET Core از IServiceCollection برای ثبت کردن سرویس‌های برنامه‌ی خودمان استفاده می‌کنیم. تمامی سرویس‌هایی را که انتظار داریم توسط DI Container به کلاس‌هایی تزریق شوند، باید درون IServiceCollection ثبت گردند. تمام سرویس‌هایی که به وسیله‌ی IServiceCollection ثبت شده‌اند، پس از ساخته شدن، توسط اینترفیس IServiceProvider قابل واکشی هستند.

بنابراین دو اینترفیس حیاتی برای کار کرد صحیح Microsoft Dependency Injection Container درون ASP.NET Core وجود دارند:
  • IServiceCollection : برای ثبت سرویس‌ها
  • IServiceProvider : برای واکشی سرویس‌ها

در ASP.NET Core معمول‌ترین مکان برای ثبت کردن سرویس‌ها درون Container، به صورت پیش فرض درون کلاس Startup و درون متد ConfigureServices انجام می‌گیرد.
به صورت پیش فرض کلاس Startup دو متد دارد:
  • ConfigureServices : برای پیکربندی و ثبت سرویس‌های درونی DI Container استفاده می‌شود.
  • Configure : برای تنظیمات pipeline میان افزارها ( Middlewares ) بکار می‌رود.

در اینجا پیاده سازی پیش فرض کلاس Startup را می‌بینیم که البته کدهای درون متد Configure را برای درگیر نکردن ذهن شما، مخفی کرده‌ایم: 
public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //  کدها جهت خوانایی بیشتر مخفی شده اند
        }
    }

همانطورکه می‌بینید، متد ConfigureService پارامتر IServiceCollection را می‌گیرد که به وسیله‌ی WebHost در زمان اجرای برنامه، مقدار دهی می‌شود.

تعداد زیادی Extension method برای IServiceCollection وجود دارند که برای پشتیبانی از ثبت کردن سرویس‌های مختلف در سناریوهای گوناگون به کار می‌روند. در اینجا ما از نسخه‌ی 3.1 چارچوب ASP.NET Core استفاده می‌کنیم. برای همین هم برای ثبت سرویس‌های پیش فرض فریمورک MVC از متد توسعه‌ی services.AddControllersWithViews()    استفاده می‌کنیم.  متد توسعه‌ی AddControllersWithViews() سرویس‌هایی را که معمولا در فریم ورک MVC استفاده می‌شوند، درون IServiceCollection ثبت می‌کند. در نسخه‌های قبلی چارچوب ASP.NET Core،  مانند نسخه‌های 2.1 و 2.2 برای این کار از متد توسعه‌ی AddMvc() استفاده می‌شد.

در Microsoft Dependency Injection Container ، معمولا  ترتیب ثبت سرویس‌ها مهم نیست.

خب، اولین سرویس اختصاصی برنامه‌ی خودمان را با چرخه‌ی حیات Transient و زیر سرویس پیشین، به شکل زیر ثبت می‌کنیم :

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddTransient<IMessageServiceA, MessageServiceAA>();
        }
همانطور که می‌بینید، در اینجا ما از متد AddTransient() استفاده کرده‌ایم. متد AddTransient() درون فضای نام Microsoft.Extensions.DependencyInjection قرار دارد. این متد Overload ‌های گوناگونی دارد و ما از نوعی از آن استفاده کرده‌ایم که دو نوع generic را می‌پذیرد و تعریف آن به صورت زیر است: 
public static IServiceCollection AddTransient<TService, TImplementation>(this IServiceCollection services)
در اینجا TService ، اینترفیس سرویس ماست. این نوع، همان نوعی است که کلاس‌های ما می‌توانند به آن وابسته باشند. پارامتر دوم، از نوع TImplemention است که پیاده سازی مورد نظر برای TService را ثبت می‌کند. TImplmention   نوعی است که Container در زمان واکشی و تزریق TService از آن نمونه سازی کرده و به کلاس مورد نظر تزریق می‌کند.

در اینجا وقتی ما برای IMessageServiceA ، پیاده سازی MessageServiceA را ثبت می‌کنیم، از این به بعد DI Container، هر زمانیکه در لیست پارامترهای سازنده‌ی یک کلاس، IMessageServiceA را مشاهده کند، بررسی می‌کند که چه کلاسی به عنوانی پیاده سازی این اینترفیس ثبت شده‌است، سپس از آن نمونه سازی می‌کند و درون سازنده‌ی مورد نظر تزریق می‌کند. خب، حالا برنامه را دوباره اجرا کنید؛ می‌بینید که برنامه اجرا می‌شود.

 
در ادامه ابتدا در مورد روش‌های مختلف ثبت سرویس‌ها و بعد روش‌های واکشی سرویس‌ها را بررسی می‌کنیم.
مطالب
ckeditor در برابر tinymce
این دو ادیتور  یعنی CKEditor و TinyMCE  دو تا از محبوب‌ترین ادیتورهای موجود تحت وب هستند که به صورت متن باز ارائه می‌شوند و از لحاظ قدرت و کارآیی در رده بالایی قرار دارند. همچنین مستندات و api‌های خوبی هم در مورد آنها موجود است؛ ولی در هنگام استفاده خیلی‌ها شاید این سؤال را داشته باشند که کدام ادیتور را انتخاب کنند؟ حتی اگر هر دو ادیتور امکاناتی بیش از نیاز ما را فراهم کنند، باز هم انسان در پی بهترین هاست. 
در این مطلب به مزایا و معایب اشاره نشده و قصد تخریب ادیتور خاصی را نداریم؛ بلکه خصوصیات آن‌ها بررسی شده و در نهایت توسعه دهنده می‌تواند بر اساس این‌ها، ابزار درستی را برای کارش، انتخاب کند.

رابط کاربری یا user interface
ckeditor داری رابط کاربری است که برای همه ادیتورها به صورت پیش فرض وجود دارد و این حالت برای خیلی از کاربرها شناخته شده هست.

در tinymce رابط کاربری به غیر از نوار ابزار میتواند شامل منو هم باشد؛ با اینکه استفاده از منو آنچنان در ادیتور مورد استقبال قرار نمی‌گیرد ولی ممکن است بعضی‌ها این ویژگی را دوست داشته باشند:

برای حذف منو در این ادیتور یا مشخص کردن چه عناصری نمایش داده شود، این کد در سایت سازنده نوشته شده:
// Disable all menus
tinymce.init({
    menubar : false
});

// Configure different order
tinymce.init({ 
    menubar: "tools table format view insert edit"
});

پلاگین‌ها Plugins
وقتی بحث پلاگین پیش کشیده می‌شود، انتخابی بین ساده بودن و اختصاصی بودن پیش خواهد آمد. به نظر می‌رسد پلاگین‌های ck در حالت عادی بیشتر از tiny هستند و امکانات بیشتری را نسبت به tiny ارائه می‌کنند؛ برای مثال به دیالوگ درج تصویر در دو شکل زیر نگاه کنید: در ck شما امکانات بیشتری برای درج یک تصویر دارید تا tiny

آپلود تصویر در ckeditor

CKEditor

tinymce

Tinymce

در نسخه‌های پیشین tiny این دیالوگ‌ها به صورت پنجره‌های popup باز می‌شدند، ولی در تمامی نسخه‌های ck بسیاری از این پنجره‌ها بدین صورت باز نشده و سفارشی هستند. درست است که در ظاهر مثل هم باز می‌شوند ولی مکانیزم و روش باز شدن آن‌ها با یکدیگر متفاوت است و در واقع ck با روشی هوشمندانه و با استفاده از کدهای جاوا اسکریپت و ترکیب لایه‌های html این روش را پیاده سازی می‌کند.

شاید بگویید خوب چه فرقی می‌کند که از چه روشی برای پیاده سازی استفاده شود؟ این مورد از دو جنبه مورد بررسی قرار می‌گیرد:

1 - اگر به مرورگرهایی چون IE دقت کرده باشید، می‌بینید که موقعی‌که یک popup باز می‌شود، به عنوان یک پنجره‌ی جدید باز می‌شود و باعث شلوغ شدن صفحه می‌گردد و بیشتر موجب آزار کاربر می‌گردد.
2- ساخت واقعی یک صفحه popup منجر شدن به ایجاد یک پنجره ای جدید و طی شدن فرآیندهای مربوط به بارگذاری و رندر شدن یک صفحه‌ی جدید می‌باشد؛ ولی در مورد پنجره‌های سفارشی این فرآیندها صورت نمی‌گیرد. این مورد بر روی بسیاری از هاست‌ها خود را به وضوح نمایش می‌دهد و ck در صرفه جویی از زمان، به مراتب بهتر عمل می‌کند. 


از نسخه tinymce 4 به بعد مکانیزم سفارشی بودن دیالوگ‌ها بر روی این ادیتور نیز صورت گرفته است.

کپی و پیست Copy - Paste

بسیاری از کاربران گاهی اوقات مطالب خود را از جایی یا از یک صفحه‌ی وب دیگر به ادیتور کپی می‌کنند. برای مثال در اینجا صفحه‌ی گوگل را به داخل ادیتورها کپی می‌کنیم و به نظر میرسد ckeditor در این مواقع رفتار بهتری نسبت به tiny از خود نشان می‌دهد.

در تاینی لوگوی گوگل دقیقا در وسط قرار گرفته و بعضی موارد خاص مانند فرم‌ها به خوبی نمایش داده می‌شوند؛ ولی در ck شما متن تمیزتری دارید و طوری نشان داده می‌شود که یک css کاربرپسند به آن اضافه شده است.

ckeditor

Ckeditor


انعطاف پذیری Flexibility

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

حجم یا سایز ادیتورها Size

با توجه به رشد اینترنت در گوشی‌های همراه و دیگر وسایل همراه، حجم منابع اینترنتی بیش از قبل مورد توجه قرار می‌گیرد. در این زمینه tinymce برگ برنده‌ای داشته و بهتر است بدانید که حجم این ویرایشگر تقریبا نصف رقیبش یعنی ckeditor می‌باشد.

حالا شما با توجه به موارد بالا می‌توانید ادیتور خود را انتخاب کنید.

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

فایل‌های زبان TinyMCE 

فایل‌های زبان CKeditor 



منابع:

http://www.krizalys.com/article/ckeditor-vs-tinymce  

http://www.tinymce.com/wiki.php/Configuration:menubar

مطالب
دات نت 4 و کلاس Lazy

یکی از الگوهای برنامه نویسی شیء گرا، Lazy Initialization Pattern نام دارد که دات نت 4 پیاده سازی آن‌را سهولت بخشیده است.
در دات نت 4 کلاس جدیدی به فضای نام System اضافه شده است به نام Lazy و هدف از آن lazy initialization است؛ من ترجمه‌اش می‌کنم وهله سازی با تاخیر یا به آن on demand construction هم گفته‌اند (زمانی که به آن نیاز هست ساخته خواهد شد).
فرض کنید در برنامه‌ی خود نیاز به شیءایی دارید و ساخت این شیء بسیار پرهزینه است. نیازی نیست تا بلافاصله پس از تعریف، این شیء ساخته شود و تنها زمانیکه به آن نیاز است باید در دسترس باشد. کلاس Lazy جهت مدیریت اینگونه موارد ایجاد شده است. تنها کاری که در اینجا باید صورت گیرد، محصور کردن آن شیء هزینه‌بر توسط کلاس Lazy است:

Lazy<ExpensiveResource> ownedResource = new Lazy<ExpensiveResource>();

در این حالت برای دسترسی به شیء ساخته شده از ExpensiveResource ، می‌توان از خاصیت Value استفاده نمود (ownedResource.Value). تنها در حین اولین دسترسی به ownedResource.Value ، شیء ExpensiveResource ساخته خواهد شد و نه پیش از آن و نه در اولین جایی که تعریف شده است. پس از آن این حاصل cache شده و دیگر وهله سازی نخواهد شد.
ownedResource دارای خاصیت IsValueCreated نیز می‌باشد و جهت بررسی ایجاد آن شیء می‌تواند مورد استفاده قرار گیرد. برای مثال قصد داریم اطلاعات ExpensiveResource را ذخیره کنیم اما تنها در حالتیکه یکبار مورد استفاده قرار گرفته باشد.
کلاس Lazy دارای دو متد سازنده‌ی دیگر نیز می‌باشد:
public Lazy(bool isThreadSafe);
public Lazy(Func<T> valueFactory, bool isThreadSafe);

و هدف از آن استفاده‌ی صحیح از این متد در محیط‌های چند ریسمانی است. بدیهی است در این نوع محیط‌ ها علاقه‌ای نداریم که در یک لحظه توسط چندین ترد مختلف، سبب ایجاد وهله‌های ناخواسته‌ا‌ی از ExpensiveResource شویم و تنها یک مورد از آن کافی است یا به قولی thread safe, lazy initialization of expensive objects
بدیهی است اگر برنامه‌ی شما چند ریسمانی نیست می‌توانید این مکانیزم را کنسل کرده و اندکی کارآیی برنامه را با حذف قفل‌های همزمانی این کلاس بالا ببرید.

مثال اول:

using System;
using System.Threading;

namespace LazyExample
{
class Program
{
static void Main()
{
Console.WriteLine("Before assignment");
var slow = new Lazy<Slow>();
Console.WriteLine("After assignment");

Thread.Sleep(1000);

Console.WriteLine(slow);
Console.WriteLine(slow.Value);

Console.WriteLine("Press a key...");
Console.Read();
}
}


class Slow
{
public Slow()
{
Console.WriteLine("Start creation");
Thread.Sleep(2000);
Console.WriteLine("End creation");
}
}
}
خروجی این برنامه به شرح زیر است:

Before assignment
After assignment
Value is not created.
Start creation
End creation
LazyExample.Slow
Press a key...

همانطور که ملاحظه می‌کنید تنها در حالت دسترسی به مقدار Value شیء slow ، عملا وهله‌ای از آن ساخته خواهد شد.

مثال دوم:
شاید نیاز به مقدار دهی خواص کلاس پرهزینه‌ وجود داشته باشد. برای مثال علاقمندیم خاصیت SomeProperty کلاس ExpensiveClass را مقدار دهی کنیم. برای این منظور می‌توان به شکل ذیل عمل کرد (یک Func<t>را می‌توان به سازنده‌ی آن ارسال نمود):

using System;

namespace LazySample
{
class Program
{
static void Main()
{
var expensiveClass =
new Lazy<ExpensiveClass>
(
() =>
{
var fobj = new ExpensiveClass
{
SomeProperty = 100
};
return fobj;
}
);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("expensiveClass.SomeProperty value {0}",
(expensiveClass.Value).SomeProperty);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("Press a key...");
Console.Read();
}
}

class ExpensiveClass
{
public int SomeProperty { get; set; }

public ExpensiveClass()
{
Console.WriteLine("ExpensiveClass constructed");
}
}
}

کاربردها:
- علاقمندیم تا ایجاد یک شیء هزینه‌بر تنها پس از انجام یک سری امور هزینه‌بر دیگر صورت گیرد. برای مثال آیا تابحال شده است که با یک سیستم ماتریسی کار کنید و نیاز به چند گیگ حافظه برای پردازش آن داشته باشید؟! زمانیکه از کلاس Lazy استفاده نمائید، تمام اشیاء مورد استفاده به یکباره تخصیص حافظه پیدا نکرده و تنها در زمان استفاده از آن‌ها کار تخصیص منابع صورت خواهد گرفت.
- ایجاد یک شیء بسیار پر هزینه بوده و ممکن است در بسیاری از موارد اصلا نیازی به ایجاد و یا حتی استفاده از آن نباشد. برای مثال یک شیء کارمند را درنظر بگیرید که یکی از خواص این شیء، لیستی از مکان‌هایی است که این شخص قبلا در آنجاها کار کرده است. این اطلاعات نیز به طور کامل از بانک اطلاعاتی دریافت می‌شود. اگر در متدی، استفاده کننده از شیء کارمند هیچگاه اطلاعات مکان‌های کاری قبلی او را مورد استفاده قرار ندهد، آیا واقعا نیاز است که این اطلاعات به ازای هر بار ساخت وهله‌ای از شیء کارمند از دیتابیس دریافت شده و همچنین در حافظه ذخیره شود؟