مطالب
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4#
در ادامه  پست قبل، در این پست به بررسی کلاس Triangle جهت رسم مثلث و کلاس Diamond جهت رسم لوزی می‌پردازیم.

using System.Drawing;

namespace PWS.ObjectOrientedPaint.Models
{
    /// <summary>
    /// Triangle
    /// </summary>
    public class Triangle : Shape
    {
        #region Constructors (2)

        /// <summary>
        /// Initializes a new instance of the <see cref="Triangle" /> class.
        /// </summary>
        /// <param name="startPoint">The start point.</param>
        /// <param name="endPoint">The end point.</param>
        /// <param name="zIndex">Index of the z.</param>
        /// <param name="foreColor">Color of the fore.</param>
        /// <param name="thickness">The thickness.</param>
        /// <param name="isFill">if set to <c>true</c> [is fill].</param>
        /// <param name="backgroundColor">Color of the background.</param>
        public Triangle(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor)
            : base(startPoint, endPoint, zIndex, foreColor, thickness, isFill, backgroundColor)
        {
            ShapeType = ShapeType.Triangle;
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="Triangle" /> class.
        /// </summary>
        public Triangle()
        {
            ShapeType = ShapeType.Triangle;
        }

        #endregion Constructors

        #region Methods (1)

        // Public Methods (1) 

        /// <summary>
        /// Draws the specified g.
        /// </summary>
        /// <param name="g">The g.</param>
        public override void Draw(Graphics g)
        {
            var points = new PointF[3];
            points[0] = new PointF(X + Width / 2, Y);
            points[1] = new PointF(X + Width, Y + Height);
            points[2] = new PointF(X, Y + Height);
            if (IsFill)
                g.FillPolygon(BackgroundBrush, points);
            g.DrawPolygon(new Pen(ForeColor, Thickness), points);
            base.Draw(g);
        }

        #endregion Methods
    }
}
همانگونه که مشاهده می‌کنید کلاس مثلث از کلاس Shape ارث برده و تشکیل شده از یک سازنده و بازنویسی (override) متد Draw می‌باشد، البته متد HasPointInSahpe در کلاس پایه قاعدتا باید بازنویسی شود، برای تشخیص وجود نقطه در شکل مثلث، (اگر دوستان فرمولش می‌دونن ممنون می‌شم در اختیار بذارن). در متد Draw سه نقطه مثلث در نظر گرفته شده که بر طبق آن با استفاده از متدهای رسم منحنی اقدام به رسم مثلث توپر یا تو خالی نموده‌ایم.

کلاس لوزی نیز دقیقا مانند کلاس مثلث عمل می‌کند.
using System.Drawing;

namespace PWS.ObjectOrientedPaint.Models
{
    /// <summary>
    /// Diamond
    /// </summary>
    public class Diamond : Shape
    {
        #region Constructors (2)

        /// <summary>
        /// Initializes a new instance of the <see cref="Diamond" /> class.
        /// </summary>
        /// <param name="startPoint">The start point.</param>
        /// <param name="endPoint">The end point.</param>
        /// <param name="zIndex">Index of the z.</param>
        /// <param name="foreColor">Color of the fore.</param>
        /// <param name="thickness">The thickness.</param>
        /// <param name="isFill">if set to <c>true</c> [is fill].</param>
        /// <param name="backgroundColor">Color of the background.</param>
        public Diamond(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor)
            : base(startPoint, endPoint, zIndex, foreColor, thickness, isFill, backgroundColor)
        {
            ShapeType = ShapeType.Diamond;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Diamond" /> class.
        /// </summary>
        public Diamond()
        {
            ShapeType = ShapeType.Diamond;
        }

        #endregion Constructors

        #region Methods (1)

        // Public Methods (1) 

        /// <summary>
        /// Draws the specified g.
        /// </summary>
        /// <param name="g">The g.</param>
        public override void Draw(Graphics g)
        {
            var points = new PointF[4];
            points[0] = new PointF(X + Width / 2, Y);
            points[1] = new PointF(X + Width, Y + Height / 2);
            points[2] = new PointF(X + Width / 2, Y + Height);
            points[3] = new PointF(X, Y + Height / 2);
            if (IsFill)
                g.FillPolygon(BackgroundBrush, points);
            g.DrawPolygon(new Pen(ForeColor, Thickness), points);
            base.Draw(g);
        }

        #endregion Methods
    }
}

این کلاس نیز از کلاس Shape ارث برده و دارای یک سازنده بوده و متد Draw را ازنو بازنویسی می‌کند، این متد نیز با استفاده از چهار نقطه و استفاده از متد رسم منحنی در دات نت اقدام به طراحی لوزی توپر یا تو خالی می‌کند، متد HasPointInSahpe در کلاس پایه قاعدتا باید بازنویسی شود، برای تشخیص وجود نقطه در شکل لوزی، برای رسم لوزی توپر نیز خصوصیت BackgroundBrush استفاده کرده و شی توپر را رسم می‌کند.

مباحث رسم مستطیل و مربع، دایره و بیضی در پست‌های بعد بررسی خواهند شد.

پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1# 
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2# 
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#

موفق وموید باشید.
مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت اول - معرفی، نصب و تعریف قواعد اعتبارسنجی
روش مرسوم اعتبارسنجی اطلاعات مدل‌های ASP.NET Core، با استفاده از data annotations توکار آن است که در بسیاری از موارد هم به خوبی کار می‌کند. اما اگر به دنبال ویژگی‌های دیگری مانند نوشتن آزمون‌های واحد برای اعتبارسنجی اطلاعات، جداسازی شرط‌های اعتبارسنجی از تعاریف مدل‌ها، نوشتن اعتبارسنجی‌های پیچیده به همراه تزریق وابستگی‌ها هستید، کتابخانه‌ی FluentValidation می‌تواند جایگزین بهتر و بسیار کاملتری باشد.


نصب کتابخانه‌ی FluentValidation در پروژه

فرض کنید پروژه‌ی ما از سه پوشه‌ی FluentValidationSample.Web، FluentValidationSample.Models و FluentValidationSample.Services تشکیل شده‌است که اولی یک پروژه‌ی MVC است و دو مورد دیگر classlib هستند.
در پروژه‌ی FluentValidationSample.Models، بسته‌ی نیوگت کتابخانه‌ی FluentValidation را به صورت زیر نصب می‌کنیم:
dotnet add package FluentValidation.AspNetCore


جایگزین کردن سیستم اعتبارسنجی مبتنی بر DataAnnotations با FluentValidation

اکنون فرض کنید در پروژه‌ی Models، مدل ثبت‌نام زیر را اضافه کرده‌ایم که از همان data annotations توکار و استاندارد ASP.NET Core برای اعتبارسنجی اطلاعات استفاده می‌کند:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class RegisterModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email")]
        [EmailAddress]
        public string Email { get; set; }

        [Range(18, 60)]
        [Display(Name = "Age")]
        public int Age { get; set; }
    }
}
برای جایگزین کردن data annotations اعتبارسنجی اطلاعات با روش FluentValidation، می‌توان به صورت زیر عمل کرد:
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.UserName).NotNull();
            RuleFor(x => x.Password).NotNull().Length(6, 100);
            RuleFor(x => x.ConfirmPassword).Equal(x => x.Password);
            RuleFor(x => x.Email).EmailAddress();
            RuleFor(x => x.Age).InclusiveBetween(18, 60);
        }
    }
}
برای این منظور ابتدا یک کلاس Validator را با ارث بری از AbstractValidator از نوع مدلی که می‌خواهیم قواعد اعتبارسنجی آن‌را مشخص کنیم، ایجاد می‌کنیم. سپس در سازنده‌ی آن، می‌توان به متدهای تعریف شده‌ی در این کلاس پایه دسترسی یافت.
در اینجا در ابتدا به ازای هر خاصیت کلاس مدل مدنظر، یک RuleFor تعریف می‌شود که با استفاده از static reflection، امکان تعریف strongly typed آن‌ها وجود دارد. سپس ویژگی Required به متد NotNull تبدیل می‌شود و ویژگی StringLength توسط متد Length قابل تعریف خواهد بود و یا ویژگی Compare توسط متد Equal به صورت strongly typed به خاصیت دیگری متصل می‌شود.

پس از این تعاریف، می‌توان ویژگی‌های اعتبارسنجی اطلاعات را از مدل ثبت نام حذف کرد و تنها ویژگی‌های خاص Viewهای MVC را در صورت نیاز باقی گذاشت:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class RegisterModel
    {
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        public string ConfirmPassword { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Display(Name = "Age")]
        public int Age { get; set; }
    }
}


تعریف پیام‌های سفارشی اعتبارسنجی

روش تعریف پیام‌های سفارشی شکست اعتبارسنجی اطلاعات را توسط متد WithMessage در ادامه مشاهده می‌کنید:
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.UserName)
                .NotNull()
                    .WithMessage("Your first name is required.")
                .MaximumLength(20)
                    .WithMessage("Your first name is too long!")
                .MinimumLength(3)
                    .WithMessage(registerModel => $"Your first name `{registerModel.UserName}` is too short!");

            RuleFor(x => x.Password)
                .NotNull()
                    .WithMessage("Your password is required.")
                .Length(6, 100);

            RuleFor(x => x.ConfirmPassword)
                .NotNull()
                    .WithMessage("Your confirmation password is required.")
                .Equal(x => x.Password)
                    .WithMessage("The password and confirmation password do not match.");

            RuleFor(x => x.Email).EmailAddress();
            RuleFor(x => x.Age).InclusiveBetween(18, 60);
        }
    }
}
به ازای هر متد تعریف یک قاعده‌ی اعتبارسنجی جدید، بلافاصله می‌توان از متد WithMessage نیز استفاده کرد. همچنین این متد می‌تواند به اطلاعات اصل model دریافتی نیز همانند پیام سفارشی مرتبط با MinimumLength نام کاربری، دسترسی پیدا کند.


روش تعریف اعتبارسنجی‌های سفارشی خواص مدل

فرض کنید می‌خواهیم یک کلمه‌ی عبور وارد شده‌ی معتبر، حتما از جمع حروف کوچک، بزرگ، اعداد و symbols تشکیل شده باشد. برای این منظور می‌توان از متد Must استفاده کرد:
using System.Text.RegularExpressions;
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.Password)
                .NotNull()
                    .WithMessage("Your password is required.")
                .Length(6, 100)
                .Must(password => hasValidPassword(password));
            //...

        }

        private static bool hasValidPassword(string password)
        {
            var lowercase = new Regex("[a-z]+");
            var uppercase = new Regex("[A-Z]+");
            var digit = new Regex("(\\d)+");
            var symbol = new Regex("(\\W)+");
            return lowercase.IsMatch(password) &&
                    uppercase.IsMatch(password) &&
                    digit.IsMatch(password) &&
                    symbol.IsMatch(password);
        }
    }
}
متد Must، می‌تواند مقدار خاصیت متناظر را نیز در اختیار ما قرار دهد و بر اساس آن مقدار می‌توان خروجی true/false ای را بازگشت داد تا نشان شکست و یا موفقیت آمیز بودن اعتبارسنجی اطلاعات باشد.

البته lambda expression نوشته شده را می‌توان توسط method groups، به صورت زیر نیز خلاصه نوشت:
RuleFor(x => x.Password)
    .NotNull()
        .WithMessage("Your password is required.")
    .Length(6, 100)
    .Must(hasValidPassword);


انتقال تعاریف اعتبارسنج‌های سفارشی خواص به کلاس‌های مجزا

اگر نیاز به استفاده‌ی از متد hasValidPassword در کلاس‌های دیگری نیز وجود دارد، می‌توان اینگونه اعتبارسنجی‌های سفارشی را به کلاس‌های مجزایی نیز تبدیل کرد. برای مثال فرض کنید که می‌خواهیم ایمیل دریافت شده، فقط از یک دومین خاص قابل قبول باشد.
using System;
using FluentValidation;
using FluentValidation.Validators;

namespace FluentValidationSample.Models
{
    public class EmailFromDomainValidator : PropertyValidator
    {
        private readonly string _domain;

        public EmailFromDomainValidator(string domain)
            : base("Email address {PropertyValue} is not from domain {domain}")
        {
            _domain = domain;
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            if (context.PropertyValue == null) return false;
            var split = context.PropertyValue.ToString().Split('@');
            return split.Length == 2 && split[1].Equals(_domain, StringComparison.OrdinalIgnoreCase);
        }
    }
}
برای این منظور یک کلاس جدید را با ارث‌بری از PropertyValidator تعریف شده‌ی در فضای نام FluentValidation.Validators، ایجاد می‌کنیم. سپس متد IsValid آن‌را بازنویسی می‌کنیم تا برای مثال ایمیل‌ها را صرفا از دومین خاصی بپذیرد.
PropertyValidatorContext امکان دسترسی به نام و مقدار خاصیت در حال اعتبارسنجی را میسر می‌کند. همچنین مقدار کل model جاری را نیز به صورت یک object در اختیار ما قرار می‌دهد.

اکنون برای استفاده‌ی از آن می‌توان از متد SetValidator استفاده کرد:
RuleFor(x => x.Email)
    .SetValidator(new EmailFromDomainValidator("gmail.com"));
و یا حتی می‌توان یک متد الحاقی fluent را نیز برای آن طراحی کرد تا SetValidator را به صورت خودکار فراخوانی کند:
    public static class CustomValidatorExtensions
    {
        public static IRuleBuilderOptions<T, string> EmailAddressFromDomain<T>(
            this IRuleBuilder<T, string> ruleBuilder, string domain)
        {
            return ruleBuilder.SetValidator(new EmailFromDomainValidator(domain));
        }
    }
سپس تعریف قاعده‌ی اعتبارسنجی ایمیل‌ها به صورت زیر تغییر می‌کند:
RuleFor(x => x.Email).EmailAddressFromDomain("gmail.com");


تعریف قواعد اعتبارسنجی خواص تو در تو و لیستی

فرض کنید به RegisterModel این قسمت، دو خاصیت آدرس و شماره تلفن‌ها نیز اضافه شده‌است که یکی به شیء آدرس و دیگری به مجموعه‌ای از آدرس‌ها اشاره می‌کند:
    public class RegisterModel
    {
        // ...

        public Address Address { get; set; }

        public ICollection<Phone> Phones { get; set; }
    }

    public class Phone
    {
        public string Number { get; set; }
        public string Description { get; set; }
    }

    public class Address
    {
        public string Location { get; set; }
        public string PostalCode { get; set; }
    }
در یک چنین حالتی، ابتدا به صورت متداول، قواعد اعتبارسنجی Phone و Address را جداگانه تعریف می‌کنیم:
    public class PhoneValidator : AbstractValidator<Phone>
    {
        public PhoneValidator()
        {
            RuleFor(x => x.Number).NotNull();
        }
    }

    public class AddressValidator : AbstractValidator<Address>
    {
        public AddressValidator()
        {
            RuleFor(x => x.PostalCode).NotNull();
            RuleFor(x => x.Location).NotNull();
        }
    }
سپس برای تعریف اعتبارسنجی دو خاصیت پیچیده‌ی اضافه شده، می‌توان از همان متد SetValidator استفاده کرد که اینبار پارامتر ورودی آن، نمونه‌ای از AbstractValidator‌های هرکدام است. البته برای خاصیت مجموعه‌ای اینبار باید با متد RuleForEach شروع کرد:
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            // ...

            RuleFor(x => x.Address).SetValidator(new AddressValidator());

            RuleForEach(x => x.Phones).SetValidator(new PhoneValidator());
        }


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



برای مطالعه‌ی بیشتر
- «FluentValidation #1»
مطالب
مقدمه‌ای بر LINQ بخش اول
کلمه‌ی LINQ مخفف Language Integrated Query یا زبان پرس و جوی یکپارچه می‌باشد. LINQ برای اولین بار در ویژوال استودیوی 2008 و دات نت فریم ورک 3.5 برای پرکردن خلع بین دنیای اشیاء برنامه نویسی (Object Oriented World) و دنیای داده‌ها (Data World) ارائه شد.

چرا LINQ؟ 
در نگاهی کلی، مزایایی که از طریق LINQ حاصل می‌شوند عبارتند از:
• کاهش حجم کدنویسی 
• درک بهتر از عملکرد کد‌های نوشته شده
• پس از یادگیری اصول LINQ به راحتی می‌توانید از این اصول پرس و جو نویسی برای کار بر روی مجموعه داده‌های مختلف استفاده کنید
• کنترل صحت کدهای پرس و جو‌ها در زمان کامپایل ( Compile-Time Type Checking )

اجزای سازنده‌ی LINQ
دو جزء اصلی سازنده‌ی LINQ عبارت است از:
  • Elements عناصر
  • Sequences توالی‌ها
توالی‌ها می‌توانند لیستی از اطلاعات مختلف باشند. هر آیتم در لیست را عنصر می‌گوییم. توالی نمونه‌ای از یک کلاس است که اینترفیس <IEnumarable<T را پیاده سازی کرده باشد. این اینترفیس تضمین می‌کند که توالی قابلیت پیمایش عناصر را دارد.
به آرایه‌ی تعریف شده‌ی زیر دقت کنید:
int[] fibonacci = {0, 1, 1, 2, 3, 5};
متغیر fibonacci در اینجا نشان دهنده‌ی توالی و هر یک از اعداد آرایه، یک عنصر محسوب می‌شوند.
توالی می‌تواند درون حافظه‌ای باشد (In Memory Object) که به آن Local Sequence می‌گویند و یا می‌تواند یک بانک اطلاعاتی SQL Server باشد که به آن Remote Sequence می‌گویند.
در حالت Remote باید اینترفیس <IQuerable<T پیاده سازی شده باشد.
پرس و جو هایی را که بر روی توالی‌های محلی اجرا می‌شوند، اصطلاحا Local Query و یا LINQ-To-Object  نیز می‌نامند.
عملگرهای پرس و جوی  زیادی به شکل متد الحاقی در کلاس System.Linq.Enumerable طراحی شده‌اند. این مجموعه از عملگرهای پرس جو را اصطلاحا Standard Query Operator می‌گویند.
نکته‌ی مهم این است که عملگرهای پرس و جو تغییری را در توالی ورودی نمی‌دهند و نتیجه‌ی خروجی یک مجموعه جدید و یا یک مقدار عددی می‌باشد.

توالی خروجی و مقدار بازگشتی Scalar
در بخش قبل گفتیم که خروجی یک پرس و جو می‌تواند یک مجموعه و یا یک مقدار عددی باشد. در مثال زیر عملگر Count را بر روی مجموعه‌ی fibonacci  اعمال کردیم و عددی که نشان دهنده‌ی تعداد عناصر مجموعه است، بعنوان خروجی بازگردانده شده است.
int[] fibonacci = { 0, 1, 1, 2, 3, 5 };
int numberOfElements = fibonacci.Count();
Console.WriteLine($"{numberOfElements}");
IEnumerable<int> distinictNumbers = fibonacci.Distinct();
Console.WriteLine("Elements in output sequence:");
foreach (var number in distinictNumbers)
{
    Console.WriteLine(number);
}
در کد بالا توسط تابع Distinct، عناصر یکتا را از توالی ورودی استخراج کرده و بازگردانده‌ایم.
خروجی برنامه‌ی فوق به شکل زیر است :
6
Elements in output sequence:
0
1
2
3
5

مفهوم Deffer Execution  (
اجرای به تاخیر افتاده )
عمده‌ی عملگر‌های پرس و جو بلافاصله پس از ایجاد، اجرا نمی‌شوند. این عملگرها در طول اجرای برنامه اجرا خواهند شد (اجرای با تاخیر). به همین خاطر می‌توان بعد از ساخت پرس و جو  تغییرات دلخواهی را به توالی ورودی اعمال کرد.
در کد زیر  قبل از اجرای پرس و جو ، توالی ورودی ویرایش شده :
int[] fibonacci = { 0, 1, 1, 2, 3, 5 };
// ایجاد پرس و جو 
IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2);
// در این مرحله پرس و جو ایجاد شده ولی هنوز اجرا نشده است
// تغییر عنصر اول توالی
fibonacci[0] = 99;
// حرکت بر روی عناصر توالی باعث اجرای پرس و جو می‌شود
foreach (var number in numbersGreaterThanTwoQuery)
{
   Console.WriteLine(number);
}
پرس و جو تا زمان اجرای حلقه‌ی Foreach اجرا نخواهد شد. خروجی مثال بالا به شکل زیر است :
99
3
5

به غیر از بعضی از عملگرها مثل Count,Min,Last سایر عملگر‌ها بصورت اجرای با تاخیر عمل می‌کنند. عملگری مثل Count باعث اجرای فوری پرس و جو می‌شود.
تعدادی عملگر تبدیل (Conversion Operator) هم وجود دارد که باعث می‌شوند پرس و جو بلافاصله اجرا شود :
• ToList
• ToArray
• ToLookup
• ToDictionary
عملگر‌های فوق پس از اجرا، خروجی را در یک ساختمان داده‌ی جدید باز می‌گردانند.
در کد زیر اصلاح توالی متغیر Fibonacci بعد از اجرای تابع ToArray صورت گرفته است.
int[] fibonacci = { 0, 1, 1, 2, 3, 5 };
// ساخت پرس و جو
IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2) .ToArray();
// در این مرحله به خاطر عملگر استفاده شده پرس و جو اجرا می‌شود
// تغییر اولین عنصر توالی
fibonacci[0] = 99;
// حرکت بر روی نتیجه
foreach (var number in numbersGreaterThanTwoQuery)
{
   Console.WriteLine(number);
}
خروجی مثال بالا:
3
5
همانطور که می‌بینید عدد 99 در خروجی مشاهده نمی‌شود. علت فراخوانی عملگر ToArray است که بلافاصله باعث اجرای پرس و جو شده و خروجی را باز می‌گرداند . به همین خاطر تغییر عنصر اول توالی ورودی، تاثیری بر روی نتیجه‌ی خروجی ندارد. 
نظرات مطالب
تاریخ شمسی با Extension Method برای DateTime
سلام خیلی خوبه منم از کلاس زیر استفاده می‌کنم
اما تو سیلورلایت چطور استفاده کنم؟

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;


public class PersianCulture : CultureInfo
{
    private readonly Calendar cal;
    private readonly Calendar[] optionals;


    public PersianCulture(): this("FA-IR", true)
    {

    }

    public PersianCulture(string cultureName, bool useUserOverride): base(cultureName, useUserOverride)
    {
        //Temporary Value for cal.
        cal = base.OptionalCalendars[0];

        //populating new list of optional calendars.
        var optionalCalendars = new List<Calendar>();
        optionalCalendars.AddRange(base.OptionalCalendars);
        optionalCalendars.Insert(0, new PersianCalendar());


        Type formatType = typeof(DateTimeFormatInfo);
        Type calendarType = typeof(Calendar);


        PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic);
        FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars",
                                                                BindingFlags.Instance | BindingFlags.NonPublic);

        //populating new list of optional calendar ids
        var newOptionalCalendarIDs = new Int32[optionalCalendars.Count];
        for (int i = 0; i < newOptionalCalendarIDs.Length; i++)
            newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null);

        optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs);

        optionals = optionalCalendars.ToArray();
        cal = optionals[0];
        DateTimeFormat.Calendar = optionals[0];

        DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
        DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
        DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
        DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };

        DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
        DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
        DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" };

        DateTimeFormat.AMDesignator = "ق.ظ";
        DateTimeFormat.PMDesignator = "ب.ظ";

        
        DateTimeFormat.ShortDatePattern = "yyyy/MM/dd";
        DateTimeFormat.LongDatePattern = "yyyy/MM/dd";
             
        DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd');
        //DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D');
        //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y');
        //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y');
        

    }

    public override Calendar Calendar
    {
        get { return cal; }
    }

    public override Calendar[] OptionalCalendars
    {
        get { return optionals; }
    }
}


مطالب
سفارشی سازی Header و Footer در PdfReport
صورت مساله:
- می‌خواهیم footer پیش فرض PdfReport را که تاریخ را در یک سمت، و شماره صفحه را در سمتی دیگر نمایش می‌دهد، به عبارت «صفحه x از n» تغییر دهیم.
- می‌خواهیم در Header گزارش بجای Header پیش فرض PdfReport یکی از قالب‌های PDF تهیه شده توسط Open Office را نمایش دهیم (و یا هر ساختار دیگری را).

تمام اجزای PdfReport جهت امکان اعمال تغییرات کلی و توسعه آن‌ها طراحی شده‌اند؛ قالب‌ها، هدر، فوتر، منابع داده، قالب‌های نمایش سلول‌ها، تعریف توابع تجمعی سفارشی و غیره. جهت سهولت کار، به ازای هر یک از این موارد، پیاده سازی‌های پیش فرضی در PdfReport قرار دارند، امکان اگر مورد رضایت شما نیستند ... از بنیان تغییرشان دهید! (و همچنین اگر مورد جالبی را پیاده سازی کردید، می‌توانید به عنوان یک وصله جدید ارائه دهید تا به پروژه اضافه شود)
ضمنا این مطالب سفارشی سازی نیاز به آشنایی با ساختار iTextSharp را نیز دارند؛ در حد ایجاد یک جدول ساده باید با iTextSharp آشنا باشید.

مدل‌های مورد استفاده:
namespace PdfReportSamples.Models
{
    public class Task
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public int PercentCompleted { set; get; }
        public bool IsActive { set; get; }
        public User Assignee { set; get; }
    }
}

using System;

namespace PdfReportSamples.Models
{
    public class User
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public string LastName { set; get; }
        public long Balance { set; get; }
        public DateTime RegisterDate { set; get; }
    }
}
توسط این مدل‌ها قصد داریم تعدادی فعالیت (Task) را که به تعدادی کاربر انتساب یافته است، نمایش دهیم. همچنین نمایش مقادیر خواص تو در تو  نیز در اینجا مد نظر است؛ برای مثال ستونی مانند این:
 column.PropertyName<Task>(x => x.Assignee.Name) 
کدهای کامل مثال را در ادامه ملاحظه خواهید نمود:
using System;
using System.Collections.Generic;
using System.Drawing;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeaderFooterPdfReport
    {
        readonly CustomHeader _customHeader = new CustomHeader();
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
            })
            .PagesHeader(header =>
            {
                header.CustomHeader(_customHeader);
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                {
                    ColumnsGap = 22,
                    ColumnsPerPage = 2,
                    ColumnsWidth = 250,
                    IsRightToLeft = false,
                    TopMargin = 7
                });
            })
            .MainTableDataSource(dataSource =>
            {
                var rows = new List<Task>();
                var rnd = new Random();
                for (int i = 1; i < 210; i++)
                {
                    rows.Add(new Task
                    {
                        Assignee = new User
                        {
                            Id = i,
                            Name = "user-" + i
                        },
                        IsActive = rnd.Next(0, 2) == 1 ? true : false,
                        Name = "task-" + i
                    });
                }
                dataSource.StronglyTypedList(rows);
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("#");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Name);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(3);
                    column.HeaderCell("Task Name");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Assignee.Name); // nested property support
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(3);
                    column.HeaderCell("Assignee");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.IsActive);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("Active");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Checkmark(checkmarkFillColor: Color.Green, crossSignFillColor: Color.DarkRed);
                    });
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(export =>
            {
                export.ToExcel();
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\CustomHeaderFooterPdfReportSample.pdf"));
        }
    }
}

به همراه Header سفارشی:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeader : IPageHeader
    {
        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> rowdata, IList<SummaryCellData> summaryData)
        {
            return null;
        }

        Image _image;
        public PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData)
        {
            if (_image == null) //cache is empty
            {
                var templatePath = AppPath.ApplicationPath + "\\data\\PdfHeaderTemplate.pdf";
                _image = PdfImageHelper.GetITextSharpImageFromPdfTemplate(pdfWriter, templatePath);
            }

            var table = new PdfPTable(1);
            var cell = new PdfPCell(_image, true) { Border = 0 };
            table.AddCell(cell);
            return table;
        }
    }
}

و Footer سفارشی استفاده شده:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomFooter : IPageFooter
    {
        PdfContentByte _pdfContentByte;
        readonly IPdfFont _pdfRptFont;
        readonly Font _font;
        readonly PdfRunDirection _direction;
        PdfTemplate _template;

        public CustomFooter(IPdfFont pdfRptFont, PdfRunDirection direction)
        {
            _direction = direction;
            _pdfRptFont = pdfRptFont;
            _font = _pdfRptFont.Fonts[0];
        }

        public void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            _template.BeginText();
            _template.SetFontAndSize(_pdfRptFont.Fonts[0].BaseFont, 8);
            _template.SetTextMatrix(0, 0);
            _template.ShowText((writer.PageNumber - 1).ToString());
            _template.EndText();
        }

        public void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            var pageSize = document.PageSize;
            var text = "Page " + writer.PageNumber + " / ";
            var textLen = _font.BaseFont.GetWidthPoint(text, _font.Size);
            var center = (pageSize.Left + pageSize.Right) / 2;
            var align = _direction == PdfRunDirection.RightToLeft ? Element.ALIGN_RIGHT : Element.ALIGN_LEFT;

            ColumnText.ShowTextAligned(
                        canvas: _pdfContentByte,
                        alignment: align,
                        phrase: new Phrase(text, _font),
                        x: center,
                        y: pageSize.GetBottom(25),
                        rotation: 0,
                        runDirection: (int)_direction,
                        arabicOptions: 0);

            var x = _direction == PdfRunDirection.RightToLeft ? center - textLen : center + textLen;
            _pdfContentByte.AddTemplate(_template, x, pageSize.GetBottom(25));
        }

        public void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData)
        {
            _pdfContentByte = writer.DirectContent;
            _template = _pdfContentByte.CreateTemplate(50, 50);
        }
    }
}

البته لازم به ذکر است که تمام این کدها به پوشه Samples سورس پروژه نیز جهت سهولت دسترسی، اضافه شده‌اند .

توضیحات:

برای پیاده سازی Header و Footer سفارشی در PdfReport نیاز خواهید داشت تا دو اینترفیس IPageHeader و IPageFooter را پیاده سازی کنید.
ساختار IPageHeader را در ذیل ملاحظه می‌کنید:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PdfRpt.Core.Contracts
{
    public interface IPageHeader
    {
        PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData);

        PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData);
    }
}

RenderingGroupHeader مرتبط است به مباحث گروه بندی اطلاعات و گزارشات master-detail که در قسمت‌های بعد به آن‌ها اشاره خواهد شد. چون در اینجا به آن نیازی نداشتیم، تنها کافی است متد متناظر با آن، null بر گرداند که در کلاس CustomHeader فوق قابل مشاهده است.
متد RenderingReportHeader به ازای تولید هر صفحه جدید، فراخوانی خواهد شد. به عبارتی می‌توانید در صفحات مختلف، هدرهای مختلفی را نمایش دهید.
خروجی هر دو متد در اینجا یک جدول از نوع PdfPTable است. بنابراین هر نوع ساختار دلخواهی را که علاقمند هستید به شکل یک PdfPTable ایجاد کرده و بازگشت دهید. این جدول در هدر صفحات ظاهر خواهد شد.
برای نمونه در کلاس CustomHeader، یک قالب تهیه شده توسط Open Office توسط متد توکار PdfImageHelper.GetITextSharpImageFromPdfTemplate دریافت و تبدیل به تصویر می‌شود. این تصویر از نوع تصاویر قابل درک توسط iTextSharp است و نه اینکه واقعا تبدیل به یک تصویر معمولی مثلا از نوع bmp شود. سپس این تصویر، در یک ردیف از جدولی قرار داده شده و این جدول بازگشت داده می‌شود.
در کل یا توسط کار با PdfPTable می‌توانید یک هدر غیرپیش فرض را طراحی کنید و یا می‌توانید توسط ابزارهای بصری مانند Open Office یک قالب خاص را برای آن تهیه کرده و به روشی که ذکر شد و کدهای آن‌را ملاحظه می‌کنید، بارگذاری و استفاده کنید. این قالب‌ها در مسیر Bin\Data سورس‌های پروژه قرار داده شده‌اند.

ساختار IPageFooter به صورت زیر است:
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Collections.Generic;

namespace PdfRpt.Core.Contracts
{
    public interface IPageFooter
    {
        void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData);

        void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);

        void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);
    }
}

برای طراحی یک Footer سفارشی کافی است اینترفیس فوق را پیاده سازی کنید که نمونه‌ای از آن‌را در کدهای کلاس CustomFooter ملاحظه می‌نمائید.
متد DocumentOpened، با وهله سازی شیء Document فراخوانی می‌شود.
متد PageFinished هر بار پیش از اتمام کار صفحه جاری و افزوده شدن آن به Document فراخوانی می‌گردد.
متد ClosingDocument، در زمان بسته شدن شیء Document فراخوانی خواهد شد.

اگر به امضای این متدها دقت کنید، شیء PdfWriter در اختیار شما قرار گرفته است که توسط آن می‌توان مستقیما بر روی فایل PDF، محتوایی را قرار داد. شیء Document نیز در دسترس است. مثلا توسط آن می‌توان اندازه دقیق صفحه را بدست آورد.
به علاوه پارامتر columnCellsSummaryData نیز امکان دسترسی به مقادیر ردیف‌های قبلی را در اختیار شما قرار می‌دهد. برای مثال اگر نیاز دارید تا بر اساس مقادیر ستون‌ها و ردیف‌های قبلی، محاسباتی را انجام داده و در پایین صفحات درج کنید، به این ترتیب دسترسی کاملی به آن‌ها، خواهید داشت.

استفاده از این کلاس‌های سفارشی نیز همواره به شکل زیر خواهد بود:
readonly CustomHeader _customHeader = new CustomHeader();
//...
.PagesFooter(footer =>
{
   footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
})
.PagesHeader(header =>
{
  header.CustomHeader(_customHeader);
})
کلا در PdfReport هر جایی متدی به نام CustomXYZ را مشاهده کردید، این متد یک اینترفیس را دریافت می‌کند. به عبارتی این امکان را خواهید داشت تا از متدهای پیش فرض مهیا صرفنظر کرده و مطابق نیاز، نسبت به پیاده سازی و استفاده از وهله جدیدی از این اینترفیس تعریف شده، اقدام کنید.
مطالب
معرفی چند پروژه‌ی مهم Typescript
فلسفه‌ی بوجود آمدن زبان Typescript یکی از شنیدنی‌ترین‌ها در دنیای برنامه‌نویسی است. به یاد دارم روزهای اولی که با این زبان آشنا شدم (زمانی که حدوداً ورژن 0.6 منتشر شده بود)، افراد زیادی در مورد این زبان و اینکه آیا اصلاً به این زبان احتیاج داریم یا نه نظرات زیادی دادند. مثلاً Douglas Crockford در مورد این زبان بعد از تعریف و تمجیدهایی که از Anders Hejlsberg کرده گفته :
I think that JavaScript's loose typing is one of its best features and that type checking is way overrated. TypeScript adds sweetness, but at a price. It is not a price I am willing to pay. 

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

چرا باید در مورد Typescript بدانیم؟

زبان Typescript نقاط قوت بسیاری دارد، از جمله‌ی آنها می‌توان به موارد زیر اشاره کرد:

  1. این زبان یکی از مشکلات اصلی JavaScript را که نبودن Type Safety می‌باشد حل کرده‌است. اگر چه زبانی که type safe نباشد بسیاری اوقات مزیت است! زبان typescript در حقیقت یک زبان gradual typing است.
  2. از آنجایی که typescript یک super set از زبان JavaScript است، برنامه‌نویس در لحظه از مزایای زبان JavaScript هم بهره‌مند است. مهم‌تر از آن این است که در زبان typescript به اقیانوس کتابخانه‌های JavaScript دسترسی دارید. این امکان در بسیاری زبان‌های دیگر جایگزین JavaScript وجود ندارد. حتی بهتر از آن، می‌تواند با این کتابخانه‌ها به‌صورت type safe برنامه بنویسید. تصور کنید که وقتی با $ در JQuery کار می‌کنید بتوانید از امکان intellisense استفاده کنید.
  3. بازهم از آنجا که typescript یک super set از JavaScript است، typescript قرار نیست به اسمبلی کامپایل شود؛ بلکه به زبان شناخته شده‌ای به نام JavaScript تبدیل می‌شود. بنابراین حتی می‌توان از آن JavaScript نیز یاد گرفت.
  4. کار با زبان typescript برای کسانی که با java یا سی شارپ آشنا هستند، راحت است. امکاناتی مانند genericها نیز در typescript وجود دارد.
  5. نقشه‌ی راه typescript با EcmaScript هماهنگ است. بنابراین از یادگرفتن این زبان ضرر نمی‌کنید چون قابلیت‌های این زبان را به احتمال زیاد در نسخه‌ی بعدی EcmaScript خواهید دید.
  6. این زبان توسط شرکت مایکروسافت پشتیبانی می‌شود، اوپن سورس است و تجربه‌ی Anders Hejlsberg در زمینه‌ی طراحی زبان‌های برنامه‌نویسی پشتیبان آن!
  7. پروژه‌های جالبی که در ادامه به معرفی آنها می‌پردازیم، با این زبان در حال توسعه هستند.

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

AngularJS 2

طبیعتاً مهم‌ترین اتفاقی که برای typescript در این روزهای اخیر افتاد این بود که تیم Angular اعلام کرد که نسخه‌ی ۲ این فریم‌ورک (که این روزها در حد JQuery در وب معروف شده و استفاده می‌شود) را با زبان Typescript توسعه می‌دهد و امکاناتی که قرار بود توسط زبان AtScript پیاده‌سازی شوند، به کمک Typescript توسعه پیدا می‌کنند. تیم Typescript هم بلافاصله اعلام کرد که در نسخه‌ی 1.5 که به‌زودی منتشر می‌شود بسیاری از امکانات AtScript قرار خواهد داشت. بنابراین می‌توانید منتظر قابلیتی شبیه به Attributeهای سی‌شارپ در typescript 1.5 باشید.
همانطور که می‌دانید AngularJS مهم‌ترین فریم‌ورک حال حاضر است که برای توسعه‌ی نرم‌افزارهای SPA وجود دارد. اعلام توسعه‌ی Angular 2 به‌وسیله‌ی Typescript مطمئناً خبر خوبی برای برنامه‌نویسان typescript خواهد بود، چون این اتفاق باعث بهبود سریع‌تر این زبان می‌شود.

Definitely Typed

اگرچه نمی‌توان این پروژه را در سطح دیگر پروژه‌هایی که در این مقاله معرفی می‌شود قرار داد، ولی اهمیت آن من را مجبور کرد که در این مقاله در موردش صحبت کنم. پروژه‌ی Definitely Typed در حقیقت استفاده از کتابخانه‌های دیگر JavaScript را در typescript ممکن می‌سازد. این پروژه برای پروژه‌های دیگری مانند JQuery، AngularJS، HighCharts، Underscore و هر چیزی که فکرش را بکنید Type Definition تعریف کرده. اگر هم کتابخانه‌ای که شما می‌خواستید در این پروژه نبود، دلیلش این است که اضافه کردن آن را به شما واگذار کرده‌اند! Type Definitionها در Typescript یکی از قابلیت‌های این زبان هستند برای اینکه بتوان با کتابخانه‌های JavaScript به‌صورت Type safe کار کرد.

shumway

حتماً از شنیدن اینکه این پروژه قرار است چه کاری انجام دهد شوکه خواهید شد! shumway که توسط موزیلا توسعه می‌یابد قرار است همان flash player باشد! البته این پروژه هنوز در مراحل اولیه‌ی توسعه است ولی اگر بخواهید می‌توانید دموی این پروژه را اینجا  ببینید.

Fayde

پروژه‌ی Fayde هم Silverlight را هدف گرفته است. البته مانند shumway موسسه‌ی معروفی از آن حمایت نمی‌کند.

Doppio

پروژه‌ی Doppio در حقیقت یک Java Virtual Machine است که روی Browser هم می‌تواند اجرا شود. از جمله کارهای جالبی که با این پروژه می‌توان کرد، کامپایل کردن کد جاوا، Disassemble کردن یک فایل class، اجرای یک فایل JAR و حتی ارتباط با JavaScript هستند.

TypeFramework

این پروژه برای افرادی خوب است که هم به NodeJS علاقمند هستند و هم به ASP.NET MVC. پروژه‌ی TypeFramework در حقیقت پیاده‌سازی مدل ASP.NET MVC در NodeJS است. Controllerها، Actionها، ActionResultها و حتی ActionFilterها با همان تعریف موجود در ASP.NET MVC در این فریم‌ورک وجود دارند.

MAYHEM

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

حرف آخر

حرف آخر اینکه به نظر می‌رسد Typescript زبانی است که ارزش وقت گذاشتن دارد و اگر خواستید Typescript را یاد بگیرید نگاه کردن به کدهای این پروژه‌ها حتماً کلاس درس پرباری خواهد بود. چه کسی می‌داند، شاید شما بخواهید در توسعه‌ی یکی از این پروژه‌ها مشارکت کنید!
نکته‌ی بعد از آخر هم اینکه اگر خواستید به‌طور جدی با این زبان برنامه‌نویسی کنید نگاهی به tslint و typedoc هم بیاندازید.
اشتراک‌ها
اضافه شدن SHA1 به SQLite 3.17.0

The new sha1(X) function computes the SHA1 hash of the input X, or NULL if X is NULL, while the sha1_query(Y) function evalutes all queries in the SQL statements of Y and returns a hash of their results. 

اضافه شدن SHA1 به SQLite 3.17.0
مطالب
روش محاسبه‌ی لحظه‌ی سال تحویل
سال قبل نتیجه‌ی جستجوی من برای یافتن فرمول محاسبه‌ی زمان سال تحویل، برای ارسال ایمیل‌های خودکار تبریک آن، در سایت‌های ایرانی حاصلی نداشت. اما واژه‌ی انگلیسی Equinox سرآغازی شد برای یافتن این الگوریتم.
نام علمی لحظه‌ی سال تحویل، Vernal Equinox است. Equinox به معنای نقطه‌ای است که یک فصل، به فصلی دیگر تبدیل می‌شود:


Equinox واژه‌ای است لاتین به معنای «شب‌های مساوی» و به این نکته اشاره دارد که در Equinox، طول شب و روز یکی می‌شوند. هر سال دارای دو Equinox است: vernal equinox و autumnal equinox (بهاری و پائیزی). البته باید درنظر داشت که Equinox بهاری در نیم کره‌ی شمالی بیشتر معنا پیدا می‌کند؛ زیرا در نیم کره‌ی جنوبی در همین زمان، پائیز شروع می‌شود.
بنابراین می‌توان enum زیر را برای تعریف این چهار ثابت رخدادهای خورشیدی تعریف کرد:
public enum SunEvent
{
    /// <summary>
    /// march equinox
    /// </summary>
    VernalEquinox,
 
    /// <summary>
    /// june solstice
    /// </summary>
    SummerSolstice,
 
    /// <summary>
    /// september equinox
    /// </summary>
    AutumnalEquinox,
 
    /// <summary>
    /// december solstice
    /// </summary>
    WinterSolstice
}

در ادامه برای محاسبه‌ی زمان equinox از فصل 27 کتاب Astronomical Algorithms کمک گرفته شده و تمام اعداد و ارقام و جداولی را که ملاحظه می‌کنید از این کتاب استخراج شده‌اند.
/// <summary>
/// Based on Jean Meeus book _Astronomical Algorithms_
/// </summary>
public static class EquinoxCalculator
{
    /// <summary>
    /// Degrees to Radians conversion factor.
    /// </summary>
    public static readonly double Deg2Radian = Math.PI / 180.0;
 
    public static bool ApproxEquals(double d1, double d2)
    {
        const double epsilon = 2.2204460492503131E-16;
        if (d1 == d2)
            return true;
        var tolerance = ((Math.Abs(d1) + Math.Abs(d2)) + 10.0) * epsilon;
        var difference = d1 - d2;
        return (-tolerance < difference && tolerance > difference);
    }
 
    /// <summary>
    /// Calculates time of the Equinox and Solstice.
    /// </summary>
    /// <param name="year">Year to calculate for.</param>
    /// <param name="sunEvent">Event to calculate.</param>
    /// <returns>Date and time event occurs as a fractional Julian Day.</returns>
    public static DateTime GetSunEventUtc(this int year, SunEvent sunEvent)
    {
        double y;
        double julianEphemerisDay;
 
        if (year >= 1000)
        {
            y = (Math.Floor((double)year) - 2000) / 1000;
 
            switch (sunEvent)
            {
                case SunEvent.VernalEquinox:
                    julianEphemerisDay = 2451623.80984 + 365242.37404 * y + 0.05169 * (y * y) - 0.00411 * (y * y * y) - 0.00057 * (y * y * y * y);
                    break;
                case SunEvent.SummerSolstice:
                    julianEphemerisDay = 2451716.56767 + 365241.62603 * y + 0.00325 * (y * y) - 0.00888 * (y * y * y) - 0.00030 * (y * y * y * y);
                    break;
                case SunEvent.AutumnalEquinox:
                    julianEphemerisDay = 2451810.21715 + 365242.01767 * y + 0.11575 * (y * y) - 0.00337 * (y * y * y) - 0.00078 * (y * y * y * y);
                    break;
                case SunEvent.WinterSolstice:
                    julianEphemerisDay = 2451900.05952 + 365242.74049 * y + 0.06223 * (y * y) - 0.00823 * (y * y * y) - 0.00032 * (y * y * y * y);
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
        else
        {
            y = Math.Floor((double)year) / 1000;
 
            switch (sunEvent)
            {
                case SunEvent.VernalEquinox:
                    julianEphemerisDay = 1721139.29189 + 365242.13740 * y + 0.06134 * (y * y) - 0.00111 * (y * y * y) - 0.00071 * (y * y * y * y);
                    break;
                case SunEvent.SummerSolstice:
                    julianEphemerisDay = 1721233.25401 + 365241.72562 * y + 0.05323 * (y * y) - 0.00907 * (y * y * y) - 0.00025 * (y * y * y * y);
                    break;
                case SunEvent.AutumnalEquinox:
                    julianEphemerisDay = 1721325.70455 + 365242.49558 * y + 0.11677 * (y * y) - 0.00297 * (y * y * y) - 0.00074 * (y * y * y * y);
                    break;
                case SunEvent.WinterSolstice:
                    julianEphemerisDay = 1721414.39987 + 365242.88257 * y + 0.00769 * (y * y) - 0.00933 * (y * y * y) - 0.00006 * (y * y * y * y);
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
 
        var julianCenturies = (julianEphemerisDay - 2451545.0) / 36525;
 
        var w = 35999.373 * julianCenturies - 2.47;
 
        var lambda = 1 + 0.0334 * Math.Cos(w * Deg2Radian) + 0.0007 * Math.Cos(2 * w * Deg2Radian);
 
        var sumOfPeriodicTerms = getSumOfPeriodicTerms(julianCenturies);
 
        return JulianToUtcDate(julianEphemerisDay + (0.00001 * sumOfPeriodicTerms / lambda));
    }
 
    /// <summary>
    /// Converts a fractional Julian Day to a .NET DateTime.
    /// </summary>
    /// <param name="julianDay">Fractional Julian Day to convert.</param>
    /// <returns>Date and Time in .NET DateTime format.</returns>
    public static DateTime JulianToUtcDate(double julianDay)
    {
        double a;
        int month, year;
 
        var j = julianDay + 0.5;
        var z = Math.Floor(j);
        var f = j - z;
 
        if (z >= 2299161)
        {
            var alpha = Math.Floor((z - 1867216.25) / 36524.25);
            a = z + 1 + alpha - Math.Floor(alpha / 4);
        }
        else
            a = z;
 
        var b = a + 1524;
 
        var c = Math.Floor((b - 122.1) / 365.25);
 
        var d = Math.Floor(365.25 * c);
 
        var e = Math.Floor((b - d) / 30.6001);
 
        var day = b - d - Math.Floor(30.6001 * e) + f;
 
        if (e < 14)
            month = (int)(e - 1.0);
        else if (ApproxEquals(e, 14) || ApproxEquals(e, 15))
            month = (int)(e - 13.0);
        else
            throw new NotSupportedException("Illegal month calculated.");
 
        if (month > 2)
            year = (int)(c - 4716.0);
        else if (month == 1 || month == 2)
            year = (int)(c - 4715.0);
        else
            throw new NotSupportedException("Illegal year calculated.");
 
        var span = TimeSpan.FromDays(day);
 
        return new DateTime(year, month, (int)day, span.Hours, span.Minutes,
            span.Seconds, span.Milliseconds, new GregorianCalendar(), DateTimeKind.Utc);
    }
 
    /// <summary>
    /// These values are from Table 27.C
    /// </summary>
    private static double getSumOfPeriodicTerms(double julianCenturies)
    {
        return 485 * Math.Cos(Deg2Radian * 324.96 + Deg2Radian * (1934.136 * julianCenturies))
               + 203 * Math.Cos(Deg2Radian * 337.23 + Deg2Radian * (32964.467 * julianCenturies))
               + 199 * Math.Cos(Deg2Radian * 342.08 + Deg2Radian * (20.186 * julianCenturies))
               + 182 * Math.Cos(Deg2Radian * 27.85 + Deg2Radian * (445267.112 * julianCenturies))
               + 156 * Math.Cos(Deg2Radian * 73.14 + Deg2Radian * (45036.886 * julianCenturies))
               + 136 * Math.Cos(Deg2Radian * 171.52 + Deg2Radian * (22518.443 * julianCenturies))
               + 77 * Math.Cos(Deg2Radian * 222.54 + Deg2Radian * (65928.934 * julianCenturies))
               + 74 * Math.Cos(Deg2Radian * 296.72 + Deg2Radian * (3034.906 * julianCenturies))
               + 70 * Math.Cos(Deg2Radian * 243.58 + Deg2Radian * (9037.513 * julianCenturies))
               + 58 * Math.Cos(Deg2Radian * 119.81 + Deg2Radian * (33718.147 * julianCenturies))
               + 52 * Math.Cos(Deg2Radian * 297.17 + Deg2Radian * (150.678 * julianCenturies))
               + 50 * Math.Cos(Deg2Radian * 21.02 + Deg2Radian * (2281.226 * julianCenturies))
               + 45 * Math.Cos(Deg2Radian * 247.54 + Deg2Radian * (29929.562 * julianCenturies))
               + 44 * Math.Cos(Deg2Radian * 325.15 + Deg2Radian * (31555.956 * julianCenturies))
               + 29 * Math.Cos(Deg2Radian * 60.93 + Deg2Radian * (4443.417 * julianCenturies))
               + 28 * Math.Cos(Deg2Radian * 155.12 + Deg2Radian * (67555.328 * julianCenturies))
               + 17 * Math.Cos(Deg2Radian * 288.79 + Deg2Radian * (4562.452 * julianCenturies))
               + 16 * Math.Cos(Deg2Radian * 198.04 + Deg2Radian * (62894.029 * julianCenturies))
               + 14 * Math.Cos(Deg2Radian * 199.76 + Deg2Radian * (31436.921 * julianCenturies))
               + 12 * Math.Cos(Deg2Radian * 95.39 + Deg2Radian * (14577.848 * julianCenturies))
               + 12 * Math.Cos(Deg2Radian * 287.11 + Deg2Radian * (31931.756 * julianCenturies))
               + 12 * Math.Cos(Deg2Radian * 320.81 + Deg2Radian * (34777.259 * julianCenturies))
               + 9 * Math.Cos(Deg2Radian * 227.73 + Deg2Radian * (1222.114 * julianCenturies))
               + 8 * Math.Cos(Deg2Radian * 15.45 + Deg2Radian * (16859.074 * julianCenturies));
    }
}
خروجی‌های زمانی ستاره شناسی، عموما بر اساس فرمت Julian Date است که آغاز آن  4713BCE January 1, 12 hours GMT است. به همین جهت در انتهای این مباحث، تبدیل Julian Date به DateTime دات نت را نیز ملاحظه می‌کنید. همچنین باید دقت داشت که خروجی نهایی بر اساس UTC است و برای زمان ایران، باید 3.5 ساعت به آن اضافه شود.

خروجی این الگوریتم را برای سال‌های 2014 تا 2022 به صورت ذیل مشاهده می‌کنید:
2014 -> 1392/12/29 20:28:08
2015 -> 1394/01/01 02:16:29
2016 -> 1395/01/01 08:01:21
2017 -> 1395/12/30 14:00:00
2018 -> 1396/12/29 19:46:10
2019 -> 1398/01/01 01:29:29
2020 -> 1399/01/01 07:21:03
2021 -> 1399/12/30 13:08:41
2022 -> 1400/12/29 19:04:37
برای نمونه زمان محاسبه شده‌ی 1394/01/01 02:16:29 با زمان رسمی اعلام شده‌ی ساعت 2 و 15 دقیقه و 10 ثانیه روز شنبه 1 فروردین 1394 و یا برای سال 93 زمان محاسبه شده‌ی 1392/12/29 20:28:08 با زمان رسمی ساعت ۲۰ و ۲۷ دقیقه و ۷ ثانیه پنجشنبه ۲۹ اسفند ۱۳۹۲، تقریبا برابری می‌کند.

کدهای کامل این پروژه را از اینجا می‌توانید دریافت کنید
 Equinox.zip