مطالب
امکان اجرای خودکار کدها در زمان بارگذاری اولیه‌ی یک اسمبلی در C# 9.0
C# 9.0 به همراه قابلیت جدیدی است به نام «module initializer» که در اصل متدی است که در زمان بارگذاری اولیه‌ی یک اسمبلی، به صورت خودکار اجرا می‌شود. عملکرد آن شبیه به سازنده‌های static کلاس‌ها است؛ اما بجای اعمال به یک کلاس، اینبار به کل اسمبلی اعمال می‌شود. این قابلیت از روزهای ابتدایی طراحی CLR وجود خارجی داشته‌، اما در C# 9.0، امکان استفاده‌ی عمومی از آن فراهم شده‌است.


روش تعریف یک module initializer

در مثال زیر، قالب ابتدایی یک ModuleInitializer را مشاهده می‌کنید:
namespace CS9Features
{
    using System.Runtime.CompilerServices;

    internal static class TestModuleInitializer
    {
        [ModuleInitializer]
        public static void MyModuleInitializer()
        {
            // put your module initializer here
        }
    }
}
متدی که قرار است به عنوان module initializer معرفی شود، باید مزین به ویژگی [ModuleInitializer] باشد و همچنین این متد باید دارای ویژگی‌های زیر نیز باشد:
- باید استاتیک باشد.
- باید بدون پارامتر باشد.
- باید خروجی آن void باشد.
- نباید به صورت جنریک تعریف شود.
- این متد باید در همان اسمبلی، قابل دسترسی باشد؛ یعنی سطح دسترسی آن باید یا public و یا internal باشد.
- نباید local function باشد.


می‌توان بیش از یک ModuleInitializer را در یک اسمبلی تعریف کرد

به مثال زیر دقت کنید:
namespace CS9Features
{
    using System.Runtime.CompilerServices;

    internal static class TestModuleInitializer
    {
        [ModuleInitializer]
        public static void MyModuleInitializer1()
        {
            // put your module initializer here
        }

        [ModuleInitializer]
        public static void MyModuleInitializer2()
        {
            // put your module initializer here
        }
    }
}
در اینجا بیش از یک متد ModuleInitializer تعریف شده‌اند. اگر بر روی ابتدای هر کدام از این متدها یک break-point را قرار دهید، مشاهده خواهید کرد که کدهای آن‌ها پیش از شروع متد Main برنامه اجرا می‌شوند. همچنین نحوه‌ی اجرای این متدها همواره مشخص و ترتیبی است؛ از بالا به پایین.
این مورد یکی از مهم‌ترین تفاوت‌های module initializer‌ها با سازنده‌های static است. ترتیب اجرای سازنده‌های static مشخص نیست و بر اساس کدهای کلاینت و زمان دسترسی به کلاس‌های مختلف، سازنده‌ی استاتیک کلاس A می‌تواند پس از سازنده‌ی استاتیک کلاس B اجرا شود و یا برعکس. اما همواره نحوه‌ی اجرای module initializer‌ها مشخص و ترتیبی است و همچنین نیازی به فراخوانی آن‌ها توسط هیچ کلاینتی نیست.


موارد کاربرد module initializer‌ها

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

با توجه به نسخه قدیمی و درخواست کاربران جهت ارائه نسخه‌ای برای پشتیبانی از NET Core. ، نسخه‌ی کاملا جدیدی از این پروژه آماده گردید.

مزایا و ویژگی‌ها
  • نصب آسان با استفاده از Nuget
  • بدون نیاز به هیچگونه وب سرویس و یا دانش پیاده سازی سیستم‌های پرداخت آنلاین 
  • پشتیبانی از درگاه‌های: ملت، ملی (سداد)، پارسیان، پاسارگاد، ایران کیش و سامان
  • انجام پرداخت، فقط با نوشتن ۳ خط کد
  • طراحی کاملا یکپارچه برای انجام عملیات پرداخت با تمامی بانک‌ها
  • رعایت نکات امنیتی پرداخت آنلاین
  • درگاه مجازی، برای شبیه سازی عملیات پرداخت 
  • امکان استفاده از پروکسی برای سرور‌های خارج از ایران در صورت نیاز
  • استفاده از تکنولوژی‌های مدرن و استاندارد
  • قابل نصب بر روی پروژه‌های: ASP.NET Core, ASP.NET MVC, ASP.NET WebForms

آنچه شما در این سری مقالات یاد خواهید گرفت:

نحوه نصب
  • ASP.NET WebForms
  • ASP.NET MVC
  • ASP.NET CORE

آموزش پایه
  • درخواست پرداخت
  • تایید پرداخت
  • بازگرداندن مبلغ پرداخت شده

تنظیمات
  • درگاه‌ها
  • HttpContext
  • پایگاه داده
  • پیام‌ها

آموزش پیشرفته
  • ایجاد یک صورت‌حساب پرداخت با استفاده از InvoiceBuilder
  • درگاه مجازی پرداخت
  • استفاده از پروکسی
  • توکن پرداخت
  • تزریق وابستگی
  • Logging

نمونه پروژه‌ها
نحوه نصب
مطالب
کار با اسناد در RavenDb 4، بازیابی اسناد
در قسمت قبل عملیات ثبت و ویرایش اسناد را بررسی کردیم. همچنین نحوه‌ی کار متد LoadAsync (و یا Load) را دیدیم. برای بازیابی یک سند، به همرا اسناد مرتبط با آن، از Load به همراه متد Include استفاده می‌کنیم.
در این مثال میخواهیم آدرس شخص مورد نظر در برنامه با کد 59 بازیابی شود.
var user = _documentSession
    .Include<User>(x => x.Apps[59].AddressId)
    .Load("Users/131-A");
var address = _documentSession.Load<Address>(user.Apps[59].AddressId)

و در صورتیکه بخواهیم تمام آدرس‌های او در تمام برنامه‌های ثبت شده را داشته باشیم، به کد زیر می‌رسیم:
var user = _documentSession
    .Include<User>(x => x.Apps.Values.Select(app => app.AddressId))
    .Load("Users/131-A");
var addresses = List<Address>();
foreach(app in user.Apps)
{
    addresses.Add(_documentSession.Load<Address>(app.AddressId)); //query‌سمت کلاینت انجام اجرا می‌شود
}

 متد Load بسیار سریع کل سند ما را بازیابی میکند اما:
  • حتما باید Id سند(ها) را داشته باشیم.
  • کل سند را بازیابی میکند.
برای رفع این دو مشکل میتوانیم از امکانات Query نویسی در RavenDb استفاده کنیم. به دلیل ذخیره سازی (ظاهرا) فله‌ای اطلاعات در NoSqlها، Query گرفتن از حجم بسیار زیاد این اطلاعات، کار زمان بری است و اجرای Query بدون Index گذاری، کار بیهوده‌ای می‌شود. به همین دلیل با هر Query که اجرا می‌شود، به صورت خودکار یک Index برای آن توسط RavenDb ایجاد شده و Query بر روی Index ایجاد شده، اجرا می‌شود. عملیات Index کردن اطلاعات بصورت اتوماتیک در اولین بار اجرای Query با توجه به حجم داده‌ها می‌تواند بسیار کند باشد. همچنین ما کنترلی بر روی مدیریت ایندکس‌های ایجاد شده نداریم.
Queryها در RavenDb به چند صورت نوشته می‌شوند:

Query
متد Query برای ایجاد Query با استفاده از Linq کاربرد دارد. به مثال زیر توجه کنید:
List<User> users = await _documentSession
    .Query<Users>()
    .Where(u => u.PhoneNumber.StartsWith("915"))
    .ToListAsync();
اجرای Query بالا ابتدا باعث ایجاد یک Index بر روی ویژگی PhoneNumber می‌شود و سپس لیست کاربران را بر می‌گرداند.
برای بازیابی اطلاعات کاربران یک برنامه میتوانیم از Dictionary خود Query بگیریم:
var users = await _documentSession.Query<AppUser>()
    .Where(u => u.Id.Equals("915"))
    .Select(u => new
    {
        u.Apps[appCode].FirstName,
        u.Apps [appCode].LastName,
    })
    .ToListAsync();
این Query در RQL که زبان پرس و جوی مخصوص RavenDb است، چیزی شبیه کد زیر می‌شود:
from Users as user
where startsWith(user.PhoneNumber, "915")
select  {
    FirstName : user.Apps ["59"].FirstName,
    LastName : user.Apps ["59"].LastName
}
مشکلی که در این Query وجود دارد این‌است که کاربرانی که شماره تماس آن‌ها با 915 شروع شده است ولی در برنامه‌ای با کد 59 ثبت نشده‌اند هم در Query بازگشت داده می‌شوند و مقادیر بازگشتی برای فیلدها هم null خواهد بود. اگر بجای ذکر صریح عبارت u. Apps [appCode].FirstName به صورت زیر عمل کنیم:
from u in _documentSession.Query<User>()
                where u.PhoneNumber.StartsWith("915")
                let app = u.Apps["59"]
                select new
                {
                    app.FirstName,
                    app.LastName,
                };
عبارت let app = u.Apps["59"] در RQL تبدیل به یک متد جاوااسکریپتی می‌شود و به کدی شبیه به کد زیر می‌رسیم:
declare function output(u) {
var app = u.Apps["59"];
return { FirstName : app.FirstName, LastName : app.LastName};
}
from Users as user
where startsWith(user.PhoneNumber, "915")
select output(user)
حالا میتوانیم Key مورد نظر در دیکشنری را هم در Query به شکل زیر دخیل کنیم:
app.FirstName,
app.LastName,
*key = u.ActiveInApps.Select(a => a.Key)
و در ادامه با استفاده از متد Search، این فیلد را که به کلید دیکشنری اشاره می‌کند، محدود کرده و بعد از آن Query خود را اجرا میکنیم:
query = query.Search(u => u.key, "59");
در صورتیکه بجای دیکشنری از آرایه استفاده کرده باشیم هم کدهای ما به همین صورت می‌باشد با کمی تغییرات مربوط به تفاوت List و Dictionary!
اما هنوز Query ما بدرستی کار نمیکند چرا که ویژگی Key در RavenDb ایندکس نشده‌است و نمیتواند این ایندکس را هم تشخیص دهد. دلیل آن هم این است که تنها ویژگی‌هایی که در مرتب سازی (Sort) و یا فیلتر مورد استفاده قرار گیرند، به ایندکس‌ها اضافه می‌شوند. برای حل این مشکل باید بصورت دستی Index خود را در RavenDb بسازیم. این کار با ارث بری از کلاس پایه‌ی AbstractIndexCreationTask شروع می‌شود و مدلی را که میخواهیم Index بر روی آن اعمال شود نیز ذکر میکنیم و بعد از آن در سازنده‌ی کلاس، Index خود را می‌سازیم:
public class User_MyIndex : AbstractIndexCreationTask<User>
{
    Map = users => 
                           from u in users
                           from app in u.Apps
                           select new
                           {
                                 Id = u.Id,
                                 PhoneNumber = u.PhoneNumber,
                                 UserName = app.Value.UserName,
                                 FirstName = app.Value.FirstName,
                                 LastName = app.Value.LastName,
                                 IsActive = app.Value.IsActive,
                                 key = app.Key
     };
}
در این ایندکس به ازای هر کاربر، تمام برنامه‌هایی که ثبت شده، بررسی شده و ایندکس می‌شوند. نکته‌ای که باید به آن توجه کنید این است که ویژگی‌های ذکر شده فقط به RavenDb نحوه‌ی بازیابی فیلدهای سند را برای Index گذاری می‌گوید و همچنان خروجی این Index از نوع User بوده و تمام سند را بازگشت میدهد و باید از متد Select در صورت نیاز استفاده کنیم. برای اعمال این ایندکس به سمت سرور از متد:
new User_MyIndex().Execute(store);
و برای ارسال چندین Index به سمت سرور از متد:
IndexCreation.CreateIndexes(typeof(User_MyIndex).Assembly, store);
استفاده می‌کنیم. اکنون اگر به Query خود این ایندکس را معرفی کنیم، خروجی ما به‌درستی فقط کاربران برنامه مورد نظر را بر می‌گرداند:
from u in _documentSession.Query<User, User_MyIndex>() ...
کلاس AbstractIndexCreationTask متدهای زیادی برای کنترل دقیق Indexها در اختیار ما قرار میدهد که پرکاربردترین آن‌ها میتوانند متدهای زیر باشند: 
Index : نحوه‌ی Index کردن هر یک از پراپرتی‌ها را مشخص می‌کند.
Store : برای مواقعی کاربرد دارد که شما می‌خواهید مقدار Index شده را برای دسترسی سریع‌تر همرا با Index ذخیره کنید.
LoadDocument: این متد Id یا لیستی از Idها را به عنوان ورودی گرفته و سند مورد نظر را بازیابی می‌کند. زمانیکه میخواهیم اسناد مرتبط را همراه با سند، Index کنیم کاربرد دارد. برای مثال وقتی میخواهیم Addressهای کاربر را که در سندی جداگانه قرار دارند، به همراه اطلاعات او در Index شرکت دهیم:
select new
{
      ...
      key = aia.Key,
      Address = LoadDocument<Address>(aia.Value.AddressId),
      // City = LoadDocument<Address>(aia.Value.AddressId).City,
};
و برای Indexکردن لیستی از اسناد مرتبط به صورت زیر از LoadDocument استفاده میکنیم:
Message = app.Messages.Select(m => LoadDocument<Message>(m).Content)
* زمانی که میخواهید کلید یک Dictionary را Index کنید و میخواهید نام فیلد آن را key قرار دهید باید از k کوچک استفاده کنید؛ چرا که Key، جزء کلمات رزرو شده‌ی RavenDb می‌باشد.

DocumentQuery
دسترسی بیشتری را بر روی Query ارسالی به سمت سرور به ما می‌دهد؛ اما  strongly typed  نیست. برای مثال Query بالا را به این صورت میتوانیم با DocumentQuery پیاده کنیم:
var users = _documentSession.Advanced.AsyncDocumentQuery<User, User_MyIndex>()
      .WhereStartsWith(nameof(AppUser.PhoneNumber), "915")
      .WhereEquals("key", appCode, exact: true)
      .SelectFields<AppUserModel>(new[] { $"Apps[{appCode}].FirstName", $"Apps[{appCode}].LastName" })
      .ToListAsync();
متدهای DocumentQuery بسیار متنوع هستند و میتوانید لیست آن‌ها را در اینجا مشاهده کنید.

MoreLikeThis (اسناد شبیه)
از رایج‌ترین کارهایی که در وب سایت‌های مطرح دیده می‌شود نمایش مطالب مرتبط با مطلب جاری می‌باشد و از آنجایی که RavenDb از Lucene.NET برای ایندکس کردن اسناد استفاده می‌کند، میتواند براحتی از MoreLikeThis موجود در پروژه‌ی Contrib آن استفاده نماید.
مدل زیر را در نظر بگیرید:
public class Post
    {
        public int Id { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        public List<string> Tags { get; set; }
        public string WriterName { get; set; }
        public string WriterId { get; set; }
    }
برای استفاده از MoreLikeThis باید ابتدا محتویات مطلب خود را با استفاده از StandardAnalyzer ایندکس گذاری کنیم. همانطور که گفته شد، برای Index کردن یک سند از کد زیر میتوانیم استفاده کنیم. با این تفاوت که نحوه‌ی آنالیز سند را نیز مشخص میکنیم:
public class Post_ByContent : AbstractIndexCreationTask<Post>
{
    public Post_ByContent()
    {
        Map = posts=> from post in posts
                      select new
                      {
                          post.Content
                      };

        Analyzers.Add(p => p.Content, "StandardAnalyzer");
    }
}
از این ایندکس در Query به همراه متد MoreLikeThis استفاده میکنیم:
List<Post> posts = _documentSession
    .Query<Post, Post_ByContent>()
    .MoreLikeThis(builder => builder
        .UsingDocument(p => p.Id == "posts/59-A")
        .WithOptions(new MoreLikeThisOptions
        {
            Fields = new[] { nameof(Post.Content) },
            StopWordsDocumentId = "appConfig/StopWords"
        }))
    .ToList();
ابتدا سندی را که میخواهیم اسناد شبیه به آن بازیابی شود، معرفی میکنیم. به اینصورت بررسی بر روی تمام فیلدهای Indexگذاری شده اعمال می‌شود. اگر بخواهیم تنظیماتی را به متد اضافه کنیم از MoreLikeThisOptions استفاده میکنیم. حداقل تنظیمات میتواند معرفی نام فیلد مورد نظر برای کاهش بار سرور و همچنین معرفی سندی که StopWordهای ما در آن قرار دارد، باشد. می‌توانید در مورد StopWordها و کاربرد آن در Lucene از این مقاله استفاده کنید. 
مطالب
انتشار عمومی localhost به صورت HTTPS توسط ngrok
شاید برای شما هم پیش آمده باشد که با Webhookها کار کنید و یا در حین اجرای پروژه‌ی وب خودتان بخواهید خروجی آن را به اطرافیان خود نشان دهید. یکی از راه‌ها این است  که پروژه را بر روی یک مخزن Git ارسال کنید و سپس دوستان خودتان را اضافه کنید تا بتوانند پروژه را دریافت و اجرا کنند. البته در این حالت شاید نخواهید کسی سورس شما را ببیند! روش دیگر این است که هاست و دامین بخرید و پروژه را بر روی آن آپلود کنید و در مرحله‌ی آخر، آدرس وبسایت را برای اطرافیان خود بفرستید که این راه، هم  پرهزینه هست و هم ممکن است پروژه کامل نشده باشد که بخواهید آن‌را آپلود کنید. اینجاست که Ngrok وارد شده و پروژه‌ی لوکال شما را بر روی Https ارائه میکند.


شروع به کار با ngrok 

- قبل از ادامه‌ی این آموزش، وارد سایت https://ngrok.com شده و بعد از ثبت نام، مطابق سیستم عاملی که دارید، فایل مخصوص آن را دریافت کنید.
- بعد از دریافت، فایل زیپ را از حالت فشرده خارج کنید. به محل فایل استخراج شده رفته و در یک مکان خالی در پوشه‌ی جاری، دکمه‌ی Shift را فشرده و سپس کلیک راست کنید و در آخر گزینه‌ی Open Command Window Here را انتخاب نمائید و یا کلا از طریق cmd وارد پوشه‌ی مربوطه شوید.
- سپس پروژه‌ی خود را توسط ویژوال استودیو (IIS Express) و یا بر روی IIS لوکال خود، اجرا کنید.

- همانطور که در تصویر مشاهده می‌کنید؛ آدرس پروژه‌ی لوکال ما به شکل زیر می‌باشد: 
 http://localhost:51095/Home/Index
توجه! این پروژه‌ی آزمایشی صرفا یک صفحه‌ی HTML خیلی ساده بوده که فقط عبارت ngrok را نشان می‌دهد.

- حال به cmd برمی‌گردیم و سپس با دستور زیر میتوانیم لوکال‌هاست خود را بر روی https به اشتراک بگذاریم:
 ngrok http [port] -host-header="localhost:[port]"
اگر کار را به درستی انجام داده باشید، خروجی شبیه به تصویر زیر را خواهید داشت:


همانطور که ملاحظه می‌فرمائید، هم http و هم https را در اختیار ما قرار می‌دهد. برای مثال اگر نیاز است با webhookهای تلگرام کار کنید، باید از آدرس https آن استفاده کنید.
در اینجا در قسمت  HTTP Requests  می‌توانید درخواست‌هایی را که فرستاده می‌شوند، ببینید و یا میتوانید با رفتن به آدرس زیر، دستورات بالا را در نمایی گرافیکی و بصورت کامل نظاره‌گر باشید:
 http://127.0.0.1:4040

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



 
مطالب
Vue.js - بحث Event Handling - آشنایی و شیوۀ استفاده از رویدادها - قسمت پنجم
در این قسمت به بحث رویدادها می‌پردازیم و اینکه به چه صورتی می‌توانیم از آن‌ها درون پروژه استفاده کنیم. فریم‌ورک Vue.js در عین سادگی می‌تواند نیاز شما را برآورده کرده و به نحو مطلوبی خروجی مناسبی را بدون هیچ دردسری، به شما تحویل دهد.
رویدادها یا همان eventها به ما این اختیار را می‌دهند که بتوانیم عمل خاصی را بر روی یک صفحه یا قسمت‌های مختلف درون پروژه اعمال کنیم و کاملا شبیه رویدادهایی است که در زبان‌های دیگر با آن کار کرده و آشنایی دارید.
برای شروع کار ابتدا نیاز است تا همانند قسمت‌های قبلی، کدهای صفحه مورد نظر خود را بنویسم و صفحه مورد نظر را ذخیره کنیم. کدها برای شروع کار، بدین شکل است که به صورت زیر نوشته و آماده شده است.
<html>
<head>
    <meta charset="UTF-8">
    <title>dotnet</title>
</head>
    <body id="dotnettips">
 <a v-on:click="message" href="bank.html">click here!</a> 
       <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.27/vue.min.js">
       </script>
       <script type="text/javascript">
     new Vue({
        el: '#dotnettips',
        data:{
        },
        methods:{
            message: function () {
                alert("hi friends");
            }
        }
    });
        </script>      
        </body>
</html>
در کدهای بالا قسمت body طبق روال همیشگی یک id را به خود اختصاص داده است و لازم است که بدانیم مشخصه‌ی id درون بدنه‌ی vue و مشخصه‌ی el به کدام بخش تعلق گرفته است.
دقت بفرمائید که ما نیاز داریم تا یک function بنویسم و رویداد مورد نظر را اجرا کنیم که جهت آزمایش به دو صفحه html نیاز است و در بخش زیر از کدهای فوق قابل مشاهده است.

vue مورد نظر و متد برای اجرای رویداد بدین شکل نوشته شده است.
  new Vue({
        el: '#dotnettips',
        data:{

        },
        methods:{
            message: function () {
                alert("hi friends");
            }
        }
    });
حال باید رویداد هدایت شود که از تگ a استفاده شده است. به صورت زیر:
<html>

<head>
    <meta charset="UTF-8">
    <title>dotnet</title>
</head>

    <body id="dotnettips">

 <a v-on:click="message" href="bank.html">click here!</a>
لازم به اشاره است که جهت معرفی رویدادی به صفحه دیگر باید از دستور v-on که vuejs در اختیار ما قرار داده است، استفاده کرد و ما نیز چنین کدی را نوشته‌ایم و صفحه مورد نظر را ارجاع داده‌ایم.
  • حتما باید نام متد به رویداد کلیک معرفی شود که در کد فوق قابل مشاهده است.
نام صفحه مورد نظر برای ارجاع : bank.html
دقت شود، درون vue که نوشته شده‌است، یک متد نیز فراخوانی شده است تا رویداد، اجرا شود و ما نام آن متد را message در نظر گرفته‌ایم. 
دو صفحه به دلخواه ساخته شده‌است که نام صفحه اول با نام cc.html و صفحه دوم با نام bank.html ایجاد شده‌اند. زمان اجرای رویداد، به صفحه دوم و دریافت پیغام hi friends، توسط تابعی که نوشته‌ایم و نمایش آن بر روی صفحه مواجه خواهیم شد. بدین معنی است که رویداد به درستی اجرا شده است و سپس به صفحه دوم هدایت می‌شویم.
تصویری از خروجی ضمیمه شده است.

مطالب
SEO در AngularJS بدون نیاز به Server Side Rendering
همه ما میدانیم برای اینکه محتوای ما به وسیله Google و سایر موتورهای جستجو index شود باید این محتوا در سمت سرور ایجاد و به کلاینت ارسال شود. مدتی بود با مقالاتی مواجه میشدم که نیازی به این کار نیست و گوگل این قابلیت را دارد تا اطلاعاتی را که سمت کلاینت پردازش و Render می‌شوند نیز index کند. تا این که خودم تصمیم گرفتم این مورد را تست کنم.
چند روز پیش شروع به بررسی SEO در AngularJS 1.x کردم. صورت مسئله‌ی من به این صورت بود که نام تعدادی شهر را با AngularJS در صفحه Render کنم، طوریکه در DOM اولیه که از سرور هدایت می‌شود، نام شهرها موجود نباشند. کد زیر را مشاهده کنید.
<html dir="rtl">
<head>
    <title>وب سایت</title>
    <script src="angular.min.js">        
    </script>    
</head>
<body ng-app="app">
    <ul ng-controller="ctrl">
        <li ng-repeat="item in list">{{item}}</li>
    </ul>
</body>
    <script>
        var app=angular.module('app',[]);
        app.controller('ctrl',function($scope,$timeout){
            $scope.list=[
                'اردبیل',
                'تهران',
                'شیراز',
                'قزوین',
            ]           
        });
    </script>
</html>
این فایل را به صورت آزمایشی در Host خودم آپلود کردم؛ با مسیر.
سپس در وب مستر گوگل، مسیر را تعریف کردم و به crawl گوگل اعلام کردم که این مسیر را index کند. بعد از مدتی متوجه شدم این صفحه با تمام نام‌های شهر‌ها index شده‌اند!
مسئله را سخت‌تر کردم و این بار به صورت مسئله اولیه این مورد را هم اضافه کردم که بعد از اینکه صفحه بارگذاری شد، بعد از 5 ثانیه، نام شهر مشهد هم به لیست DOM‌ها اضافه شود و به کد بالا این کد را هم اضافه کردم (این کار را برای شبیه سازی درخواست AJAX انجام دادم):
$timeout(function(){
     $scope.list.push('مشهد')
},5000);
بعد از مدت کوتاهی متوجه شدم نام شهر مشهد هم در گوگل index شده است.این لینک را مشاهده کنید.
البته نیاز به بررسی دقیقتر این مسئله هست و باید در پروژه‌های واقعی این مورد را بررسی کرد تا safe بودن این قابلیت گوگل مورد تایید قرار بگیرد. در حال حاضر برای SEO در ReactJS و AngualrJS و VueJS از Render سمت Server استفاده میکنم. اگر این قابلیت به طور 100% جوابگوی SEO باشد، دیگر نیازی نیست Developer‌ها سمت سرور و کلاینت، کارهای تکراری برای SEO انجام دهند.
مطالب
استفاده از کتابخانه جی کوئری در الکترون
از آنجا که الکترون از مفاهیم وب در دسکتاپ به خوبی پشتیبانی می‌کند، پس به راحتی می‌توان از کتابخانه‌های تحت وب و جاوااسکرپیتی چون جی کوئری و آنگولار و ... استفاده کرد. پروژه‌ای داریم که در آن، حین باز شدن صفحه، به کاربر پیام خوش آمد گویی نشان داده می‌شود:
<!DOCTYPE html>
<html>
  <head>

    <script src="./jquery.min.js"></script>
 <meta charset="utf-8">
    <title></title>
    <script>
$(document).ready(()=>
{
   alert("Welcome");
});
    </script>
  </head>
  <body>
  </body>
</html>
برنامه را اجرا می‌کنیم و در کمال تعجب می‌بینیم که پیامی نمایش داده نمی‌شود. برای اینکه بتوانیم اشکال آن را پیدا کنیم بهتر است ابزارهای سودمند توسعه و دیباگینگ کرومیوم را فراخوانی کنیم. برای باز کردن این پنجره، بعد از ایجاد شیء پنجره (فرضا نام متغیر win باشد) عبارت زیر را می‌نویسیم:
  win.openDevTools();
برنامه را بار دیگر اجرا کنید. در سمت راست برنامه یک پنجره جدید باز می‌شود تا بتوانید از طریق آن به دیباگینگ Render Process بپردازید. اگر اینبار به پنجره کنسول نگاهی بیندازید متوجه می‌شوید که خطای داده شده به دلیل عدم شناخت $ بوده است؛ در صورتی که همه چیز به طور صحیح قرار گرفته است و در یک صفحه وب عادی خیلی راحت اجرا می‌شود، پس مشکل از کجاست؟


module و module.exports
در اکثر کتابخانه‌های جاوااسکریپتی شما با عبارت require زیاد مواجه شده‌اید و این امکان از طریق شیءایی به نام module.exports امکان پذیر شده است. شما با کدی مشابه زیر:
exports.sayHelloInEnglish=()=>
{
  console.log("hello");
}
exports.sayHelloInPersian=()=>
{
  console.log('salam');
}
می‌توانید کتابخانه خود را به این شکل در الکترون صدا بزنید:
const test=require("./test.js");
test.sayHelloInPersian();
test.sayHelloInEnglish();
همانطور که می‌بینید شیء module.exports از این طریق می‌تواند در دسترس دیگران قرار بگیرد. حالا جی کوئری و هر کتابخانه مشابهی که کدی شبیه به کد زیر را داشته باشد می‌تواند به چنین مشکلی دچار شود:
if ( typeof module === "object" && typeof module.exports === "object" ) {
  // set jQuery in `module`
} else {
  // set jQuery in `window`
}

کد بالا بررسی می‌کند که آیا  module.export وجود دارد یا خیر. اگر وجود داشته باشد، در اختیار آن قرار می‌گیرد و اگر نداشته باشد در اختیار شیء window قرار می‌گیرد. پس کاری که جی کوئری اینجا انجام می‌دهد این است که توابع آن در اختیار شیء window نیست و در اختیار exports است. به همین علت ما باید شیء جی کوئری را از طریق آن دریافت کنیم. پس کد زیر را قبل از کدهای جی کوئری می‌نویسیم:
<script src="./jquery.min.js"></script>
<script>
window.$=window.jQuery=module.exports;
</script>
یا
window.$ = window.jQuery = require('./jquery.min.js');
حالا دیگر جی کوئری به راحتی اجرا خواهد شد.
مطالب
SASS #1

SASS چیست؟

SASS مخفف Syntactically Awesome Style Sheets است که توسط آقای Hampton Catlin طراحی و ایجاد شده است و همانند CoffeeScript که پس از کامپایل به جاوااسکریپت تبدیل می‌شد، SASS نیز پس از کامپایل به CSS تبدیل می‌شود. SASS با استفاده از متغیرها، mixins، ارث بری و قوانین تودرتو، CSS را با مهارت زیادی در بهترین حالت تولید می‌کند.

SASS باعث کمتر نوشتن کد CSS، سبب افزایش خوانایی و دستکاری کردن راحتتر و پویای آن می‌شود. این مساله راهی عالی برای نوشتن کدهای CSS کاربردی‌تر است و می‌تواند سرعت گردش کار هر توسعه دهنده و یا طراح وب را افزایش دهد.

وقتی اولین بار SASS عرضه شد، syntax آن تفاوت قابل توجهی با CSS داشت (پسوند فایل‌های آن SASS. است) که به جای نوشتن براکت‌ها، از تورفتگی استفاده می‌شد و دیگر نیازی به نوشتن ";" نبود. البته با عدم استقبال از این syntax مواجه شد و با عرضه‌ی نسخه 3 SASS، (که پسوند فایل‌های آن SCSS. است) syntax آن بسیار شبیه به CSS شد؛ البته با همه‌ی ویژگی‌های SASS.

برای مثال کد CSS زیر را می‌خواهیم به دو روش بنویسیم:

header {
     margin: 0;
     padding: 0;
     color: #fff;
}
با استفاده از روش SCSS. (روش جدید)
$color:  #fff;
header {
    margin: 0;
    padding:0;
    color: $color;
}
با استفاده از روش SASS. (روش قدیم)
$color: #fff
header
   margin: 0
   padding: 0
   color: $color
همانطور که مشاهده می‌کنید برای نوشتن مقدار color از متغیر color$ استفاده کردیم . در ادامه به قابلیت‌های SASS خواهیم پرداخت.

توجه: syntax ایی که در این سری آموزشی با آن کار می‌کنیم SCSS. است.

کامپایل کردن SASS

روش‌های مختلفی برای کامپایل فایل‌های SASS وجود دارند:
  • روش اصلی استفاده از SASS در Ruby است که پس از نصب Ruby و اجرای فرمان SASS ،gem install sass نصب می‌شود و برای کامپایل، اجرای فرمان زیر:
sass myfile.scss myfile.css
  • استفاده از برنامه‌های گرافیکی مانند Hammer , CodeKit و Compass.
  • استفاده از برنامه‌های رایگان مانند libsass که با یک کامپایلر سریع نوشته شده با ++C/C است و همچنین می‌توانید libsass را از طریق NPM با node-sass   نصب کنید.
npm install node-sass
نکته: در صورتیکه می‌خواهید با استفاده از Ruby کار کامپایل را انجام دهید در هنگام نصب Ruby گزینه‌ی "Add Ruby executables to your PATH" را تیک بزنید.

خب سوالی که ممکن است برای شما پیش آمده باشد این است که باید از کدام یک از این روش‌ها را استفاده کنیم؟
بستگی به این دارد که شما چه کاری را می‌خواهید انجام دهید.
  • در صورتیکه بر روی یک پروژه‌ی بزرگ با میزان کد زیاد کار می‌کنید، استفاده از Ruby SASS، کمی کند کار کامپایل را انجام می‌دهد.
  • اگر بخواهید از libsass استفاده کنید، این مسئله وجود دارد که به طور %100 با قابلیت‌های Ruby SASS برابری ندارد.
  • در صورتیکه نمی‌خواهید از command line استفاده کنید، برنامه‌های گرافیکی گزینه‌ای عالی هستند. شما می‌توانید طوری تنظیم کنید که تمامی تغییراتی که در فایل SASS انجام می‌شود، به صورت خودکار کار کامپایل انجام شود.
  • اگر هم فقط می‌خواهید کدی را که نوشته‌اید تست کنید، می‌توانید از ابزارهای آنلاین مانند SassMeister استفاده کنید.
مطالب
CoffeeScript #5

Classes

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

در پشت صحنه، CoffeeScript برای ایجاد کلاس از prototype استفاده می‌کند. برای ساختن کلاس در CoffeeScript از کلمه کلیدی class باید استفاده کنید.

class Animal
نتیجه‌ی کامپایل مثال بالا می‌شود:
var Animal;
Animal = (function() {
  function Animal() {}
  return Animal;
})();
در مثال بالا، Animal نام کلاس و همچنین نامی است که برای ایجاد یک نمونه از آن می‌توانید استفاده کنید. در پشت صحنه CoffeeScript با استفاده از سازنده توابع این کار را انجام می‌دهد. یعنی شما می‌توانید با استفاده از کلمه کلیدی new  یک نمونه از کلاس نوشته شده را بسازید.
animal = new Animal
تعریف سازنده برای کلاس بسیار ساده است. فقط کافی است از کلمه کلیدی constructor به عنوان یک تابع در کلاس تعریف شده استفاده کنید. این تابع شبیه به initialize در Ruby و __init__ در Python است.
class Animal
  constructor: (name) ->
    @name = name
نتیجه کامپایل کد بالا می‌شود:
var Animal;
Animal = (function() {
  function Animal(name) {
    this.name = name;
  }
  return Animal;
})();
همچنین CoffeeScript امکان خلاصه نویسی را در سازنده کلاس نیز ایجاد کرده است. برای اینکار با اضافه کردن @ به آرگومان‌های تابع سازنده به صورت پیشوند این کار انجام می‌شود. مثال زیر معادل مثال قبل است که به صورت دستی مقدار دهی انجام شده است.
class Animal
  constructor: (@name) ->
و برای استفاده از این کلاس
animal = new Animal "Cat"
alert "Animal is a #{animal.name}"

Instance properties

اضافه کردن property به یک کلاس بسیار ساده و راحت است، syntax این کار دقیقا مانند اضافه کردن property به یک object است. فقط نکته ای که باید رعایت شود میزان تو رفتگی property نوشته شده است که به طور صحیح در داخل بدنه کلاس قرار بگیرد.
class Animal
  price: 5

  sell: (customer) ->

animal = new Animal
animal.sell(new Customer)
نتیجه کامپایل کد بالا می‌شود:
var Animal, animal;

Animal = (function() {
  function Animal() {}

  Animal.prototype.price = 5;

  Animal.prototype.sell = function(customer) {};

  return Animal;

})();

animal = new Animal;
animal.sell(new Customer);

Static properties

برای تعریف property به صورت static باید کلمه کلیدی this را به ابتدای آن اضافه کنید.
class Animal
  this.find = (name) ->

Animal.find("Dog")
در قسمت‌های قبل گفته شد، که به جای this می‌توان از @ استفاده کرد، در اینجا نیز می‌توان چنین کاری را انجام داد.
class Animal
  @find: (name) ->

Animal.find("Dog")
نتیجه‌ی کامپایل آن می‌شود:
var Animal;

Animal = (function() {
  function Animal() {}

  Animal.find = function(name) {};

  return Animal;

})();

Animal.find("Dog");
مطالب
استفاده از قالب ویژوال استودیو 2013 برای برنامه‌های ویندوز فرم
در این نوشتار قصد داریم تا Theme ویژوال استودیو 2013 را برای برنامه‌های ویندوز شبیه سازی کنیم. در مرحله اول یک پروژه از نوع ClassLibrary می‌سازیم و پس از آن یک کلاس که از کلاس ToolStripProfessionalRenderer ارث بری کند را ایجاد می‌کنیم. در اینجا ما نام کلاس را BlueMenuStrip انتخاب می‌کنیم. از این کلاس برای تغییر رنگ منوها استفاده می‌شود. سپس متد OnRenderMenuItemBackground آن‌را Override می‌کنیم. 
using System.Drawing;
using System.Windows.Forms;

namespace Navasser.Theme.VisualStudio
{
    public class BlueMenuStrip:ToolStripProfessionalRenderer
    {
        protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
        {
            var borderColor = ColorTranslator.FromHtml("#E5C365");//Menu Item Border
            var selectedMenuBackColor = ColorTranslator.FromHtml("#FDF4BF");//Menu Item Background
            var menuOpenedBackColor = ColorTranslator.FromHtml("#EAF0FF");
            var borderPen = new Pen(borderColor);   

            if (e.Item.Selected)//اگر آیتمی از منوها انتخاب شد
            {
                var selectedMenuBrush = new SolidBrush(selectedMenuBackColor);
                var selectedItemBounds = new Rectangle(Point.Empty, e.Item.Size);//اقدام به پر کردن یک مستطیل به اندازه ابعاد آیتم انتخاب شده می‌کند
                e.Graphics.FillRectangle(selectedMenuBrush,selectedItemBounds);
                e.Graphics.DrawRectangle(borderPen,0,0,selectedItemBounds.Width-1,selectedItemBounds.Height-1);// بوردر آیتم را رسم میکند
                e.Item.BackColor = menuOpenedBackColor;
            }
            else
            {
                base.OnRenderMenuItemBackground(e);
                e.ToolStrip.BackColor = ColorTranslator.FromHtml("#EAF0FF");
            }           
        }     
    }
}
تکه کد بالا فقط برای تغییر رنگ زمینه‌ی منوها بکار می‌رود. اما برای تغییر رنگ ToolStrip‌ها یک کلاس جدید ایجاد می‌کنیم که از کلاس ToolStripProfessionalRenderer ارث بری کرده باشد و متدهای OnRenderToolStripBackground و OnRenderButtonBackground  آن‌را Override میکنیم.
using System.Drawing;
using System.Windows.Forms;

namespace Navasser.Theme.VisualStudio
{
    public class BlueToolStrip : ToolStripProfessionalRenderer
    {
        protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
        {
            var toolStripBackColor = ColorTranslator.FromHtml("#D6DBE9");
            var toolStripBrush = new SolidBrush(toolStripBackColor);
            e.Graphics.FillRectangle(toolStripBrush,0,0,e.AffectedBounds.Width + 10,e.AffectedBounds.Height);
        }

        protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e)
        {
            var borderColor = ColorTranslator.FromHtml("#E5C365");//For border
            var selectedToolItemBackColor = ColorTranslator.FromHtml("#FDF4BF");
            var selectedItemBrush = new SolidBrush(selectedToolItemBackColor);
            var pressedItemBackColor = ColorTranslator.FromHtml("#FFF29D");
            var pressedItemBrush = new SolidBrush(pressedItemBackColor);
            var borderPen = new Pen(borderColor);            

            if (e.Item.Selected)
            {
                e.Graphics.FillRectangle(selectedItemBrush,0,0,e.Item.Width,e.Item.Height);
                e.Graphics.DrawRectangle(borderPen,0,0,e.Item.Width-1,e.Item.Height-1);
            }

            if (e.Item.Pressed)
            {
                e.Graphics.FillRectangle(pressedItemBrush, 0, 0, e.Item.Width, e.Item.Height);
                e.Graphics.DrawRectangle(borderPen, 0, 0, e.Item.Width - 1, e.Item.Height - 1);
            }
        }
    }
}
سپس Dll ایجاد شده را در برنامه خود Reference دهید و از Themeهای ایجاد شده استفاده نمایید. نتیجه‌ی کدهای بالا به شکل زیر است:


 همچنین می‌توانید برای انتخاب رنگ‌های دلخواه خودتان از ابزار ColorSchemer Studio استفاده کنید.