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

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

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

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

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

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


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

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

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


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

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

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

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

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

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

در تهیه‌ی این مجموعه مقالات از منابع زیر استفاده شده است:

1.  Pro JavaScript Techniques  (John Resig ، خالق JQuery  – فصول 2 و 3)

2.  Professional JavaScript for Web Developers (Third Edition)  (Nicholas C. Zakas  – فصول 3، 4، 5، 6 و 7)

3.  Object-Oriented JavaScript  (Stoyan Stefanov  – فصول 3، 4، 5، 6 و 8)

4.  و تجربه‌ی ناچیز اینجانب


توابع  (Functions)

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

function <functionName> ([arg0, arg1, …, argN]) {
<statements>
}

به عنوان مثال:

function sayHello(name, message) {
   alert("Hello " + name + ", " + message);
}

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

sayHello("Meysam", "welcome to site");

خروجی این تابع  “Hello Meysam, welcome to site”  می باشد که به صورت یک پنجره‌ی پیغام، نمایش می‌یابد. تابع فوق هیچ مقداری را به عنوان خروجی به برنامه‌ی اصلی یا محل فراخوانی خود بر نمی‌گرداند. اگر بخواهیم توسط تابع مقداری را برگردانیم می‌توانیم از دستور return  برای این منظور استفاده نماییم. به مثال زیر توجه کنید:

function sum(num1, num2) {
   return num1 + num2;
}

تابع  sum  دو عدد را به عنوان آرگومان ورودی دریافت نموده و حاصل جمع آن‌ها را توسط دستور  return  به عنوان خروجی بر می‌گرداند. تابع فوق را می‌توان به صورت زیر فراخوانی نمود:

alert(sum(10, 20));

خروجی :

30

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

function sum(num1, num2) {
   return num1 + num2;
   alert("Hello");
}

در مثال فوق دستور  alert به هیچ عنوان اجرا نخواهد شد؛ زیرا تابع با رسیدن به دستور return خاتمه می‌یابد. یک تابع می‌تواند شامل بیش از یک دستور return باشد.

function diff(num1,num2) {
   if (num1 > num2)
     return num1 - num2;
   else
     return num2 - num1;
}

تابع فوق اختلاف دو عدد را بدست می‌آورد و اگر عدد اول بزرگتر باشد، عدد دوم را از عدد اول تفریق می‌کند؛ در غیر اینصورت عدد اول را از عدد دوم تفریق می‌کند و به عنوان خروجی بر می‌گرداند.  نکته‌ی دیگری که لازم است بدانید این است که دستور  return  می تواند هیچ مقداری را بر نگرداند و به تنهایی بکار گرفته شود. در این صورت دقیقا بعد از دستور  return  سمی کالن  (;)  قرار می‌دهیم.

function checkNumber(num) {
   if (isNaN(num)) {
     alert("Not a number");
     return;
   }
   alert(num+" is a number");
}

تابع فوق یک ورودی را دریافت می‌نماید و در صورتی که آرگومان ورودی عدد نباشد پیغام  “Not a number”  را نمایش می‌دهد و از تابع خارج می‌شود. در صورتی که آرگومان ورودی یک عدد باشد، پیغام دوم را نمایش می‌دهد.

توجه:

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

function sum(num1, num2) {
   if (isNaN(num1) || isNaN(num2))
     return; // بهتر است حداقل مقدار 0 برگردانده شود
   return num1 + num2;
}


کار با آرگومان ها

رفتار آرگومان‌ها در جاوا اسکریپت نسبت به سایر زبان‌های برنامه نویسی کاملا متفاوت می‌باشد. در جاوا اسکریپت تعداد و نوع آرگومانهای ارسالی بررسی نمی‌شوند و خطایی هم رخ نخواهد داد. به عنوان مثال اگر تابعی با 3 آرگومان ورودی دارید، می‌توانید با 0 تا 3 آرگومان ورودی، آن تابع را فراخوانی نمایید. زیرا آرگومان‌ها در سیستم داخلی جاوا اسکریپت به صورت یک آرایه ارسال می‌گردند و تابع توجه نمی‌کند که کدام آرگومانها به این آرایه ارسال شده‌اند.

با توجه به قابلیتی که در مورد آرگومانها ذکر شد و به دلیل عدم مدیریت نوع و تعداد آرگومان‌های ارسالی، مطمئنا جهت جلوگیری از بروز خطا در توابع، باید تعداد و نوع آرگومان‌های ارسالی بررسی و مدیریت شوند. همچنین در هر تابع، آرایه‌ای به نام arguments  به صورت توکار تعبیه شده است که مدیریت آرگومان‌ها را تسهیل می‌بخشد. به مثال زیر توجه کنید:

function sayHello() {
   alert("Hello " + arguments[0] + ", " + arguments[1]);
}

sayHello("Meysam", "welcome to site");

خروجی :

"Hello Meysam, welcome to site"

تابع فوق هیچ آرگومان ورودی ندارد ولی با دو آرگومان ورودی فراخوانی شده است. در داخل تابع توسط آرایه arguments  به آرگومانهای ارسالی دسترسی پیدا کردیم. حال به مثال زیر توجه کنید:

function sum() {
   var s = 0;
   for (var i = 0; i < arguments.length; i++)
     s += arguments[i];
   return s;
}

alert(sum());
alert(sum(10, 20, 30, 40, 50));
alert(sum(10));

خروجی :

0

150

10

در تابع فوق هیچ آرگومان ورودی وجود ندارد ولی این تابع را با 0، 5 و 1 آرگومان ورودی فراخوانی نمودیم. این تابع مجموع چند عدد را محاسبه و بر می‌گرداند و می‌تواند به تعداد نامحدودی عدد دریافت نماید. البته بهتر است نوع آرگومانهای ارسالی نیز بررسی شوند تا خطایی در محاسبات رخ ندهد. همچنین بجای حلقه for  از حلقه for…in  استفاده خواهم کرد.

function sum() {
   var s = 0;
   for (var i in arguments) {
     if (isNaN(arguments[i]))
       continue;
    s += arguments[i];
   }
   return s;
}

alert(sum());
alert(sum(10, 20, "a", 40, 50));
alert(sum(10));

خروجی :

0

120

10

اگر دقت کرده باشید در فراخوانی دوم، رشته  “a”  به تابع ارسال شده است و چون آرگومانهای نامعتبر را مدیریت نموده‌ایم، خطایی در خروجی رخ نمی‌دهد. به مثال زیر نیز توجه نمایید:

function sum(a,b,c) {
   return a + b + c;
}

alert(sum(10, 20, 30));
alert(sum(10, 20));
alert(sum());

خروجی :

60

NaN

NaN

تابع فوق دارای 3 آرگومان ورودی می‌باشد؛ ولی ما این تابع را با 2 و 0 آرگومان ورودی فراخوانی نمودیم که خروجی نامناسبی را تولید نموده است. برای رفع این مشکل و معتبر سازی آرگومان‌های ارسالی می‌توانیم به صورت زیر عمل نماییم:

function sum(a, b, c) {
   if (isNaN(a)) a = 0;
   if (isNaN(b)) b = 0;
   if (isNaN(c)) c = 0;
   return a + b + c;
}

alert(sum(10, 20, 30));
alert(sum(10, 20));
alert(sum());

خروجی :

60

30

0

در تابع، قبل از انجام عمل محاسبه، بررسی کردیم که آرگومانهای ارسالی مقدار دهی شده باشند. در صورت عدم مقداردهی و یا مقداردهی نامناسب، آرگومان ارسالی را با صفر مقداردهی می‌نماید. اگر آرگومان مورد نظر به تابع ارسال نشود، مقدار پیش فرض آن undefined می‌باشد.

با توجه به مسائل مطرح شده در مورد توابع، این روش استفاده و کاربرد توابع، جزء معایب توابع در جاوا اسکریپت محسوب نمی‌شود. این تکنیک استفاده از توابع، موجب افزایش انعطاف پذیری توابع و آزادی عمل برنامه نویس می‌گردد که لذت بیشتری را به برنامه نویسی می‌دهد.

توجه:

به آرگومانهایی که در تابع دارای نام می‌باشند و یا به عبارتی، آرگومانهایی که نام آنها در تابع ذکر می‌شود، Named Arguments  یا آرگومانهای نامی (اسمی و یا نامدار) می‌گویند. مثل آرگومان های a ، b  وc  در تابع sum


عدم پشتیبانی از سربارگذاری یا  Overloading

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

اگر دو تابع هم نام داشته باشیم، تابعی که دیرتر تعریف می‌شود، جایگزین تابع قبلی می‌گردد. به مثال زیر توجه کنید:

function calc(num1,num2) {
   return num1 + num2;
}

function calc(num1,num2) {
   return num1 - num2;
}

alert(calc(200,100));

خروجی :

100

همانطور که در مثال فوق مشاهده می‌نمایید، تابع دوم فراخوانی شده‌است و حاصل تفریق به عنوان خروجی نمایش یافته است. 

نظرات مطالب
خواندنی‌های 27 شهریور
برنامه DBDocumenter رو یه نگاه بهش انداختم، جالب بود، البته یه سری چیزها هم باید اضافه بشه(شاید خودم اضافه کردم). ولی چند تا نکته در مورد سورس به نظرم رسید گفتم بگم، ببینم نظر شما چیه.
BLL رو دوقسمت کرده که قسمت Entities به نظرم Struct باید میشد تا Class.
از نظر اینکه مدل سازی کرده باشه اشیاء موجود رو بیشتر دسته بندی کرده و پسوندManager داده بهشون!
متد Static زیاد داره(به نظرم تعداد staticها رابطه مستقیمی با ماکارونی شدن کد داره و همینطور افزایش Coupling).
در کل: کد استانداردی نوشته بود.

از شما هم به خاطر معرفی اش ممنونم.
مطالب
به روز رسانی ساده‌تر اجزاء ارتباطات در EF Code first به کمک GraphDiff
دو نوع حالت کلی کارکردن با EF وجود دارند: متصل و منقطع.
در حالت متصل مانند برنامه‌های متداول دسکتاپ، Context مورد استفاده در طول عمر صفحه‌ی جاری زنده نگه داشته می‌شود. در این حالت اگر شیءایی اضافه شود، حذف شود یا تغییر کند، توسط EF ردیابی شده و تنها با فراخوانی متد SaveChanges، تمام این تغییرات به صورت یکجا به بانک اطلاعاتی اعمال می‌شوند.
در حالت غیرمتصل مانند برنامه‌های وب، طول عمر Context در حد طول عمر یک درخواست است. پس از آن از بین خواهد رفت و دیگر فرصت ردیابی تغییرات سمت کاربر را نخواهد یافت. در این حالت به روز رسانی کلیه تغییرات انجام شده در خواص و همچنین ارتباطات اشیاء موجود، کاری مشکل و زمانبر خواهد بود.
برای حل این مشکل، کتابخانه‌ای به نام GraphDiff طراحی شده‌است که صرفا با فراخوانی متد UpdateGraph آن، به صورت خودکار، محاسبات تغییرات صورت گرفته در اشیاء منقطع و اعمال آن‌ها به بانک اطلاعاتی صورت خواهد گرفت. البته ذکر متد SaveChanges پس از آن نباید فراموش شود.


اصطلاحات بکار رفته در GraphDiff

برای کار با GraphDiff نیاز است با یک سری اصطلاح آشنا بود:

Aggregate root
گرافی است از اشیاء به هم وابسته که مرجع تغییرات داده‌ها به شمار می‌رود. برای مثال یک سفارش و آیتم‌های آن‌را درنظر بگیرید. بارگذاری آیتم‌های سفارش، بدون سفارش معنایی ندارند. بنابراین در اینجا سفارش aggregate root است.

AssociatedCollection/AssociatedEntity
حالت‌های Associated به GraphDiff اعلام می‌کنند که اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root نباید به روز رسانی شوند. در این حالت تنها ارجاعات به روز رسانی خواهند شد.
اگر خاصیت راهبری از نوع ICollection است، حالت AssociatedCollection و اگر صرفا یک شیء ساده است، از AssociatedEntity استفاده خواهد شد.

OwnedCollection/OwnedEntity
حالت‌های Owned به GraphDiff اعلام می‌کنند که جزئیات و همچنین ارجاعات اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root باید به روز رسانی شوند.


دریافت و نصب GraphDiff

برای نصب خودکار کتابخانه‌ی GraphDiff می‌توان از دستور نیوگت ذیل استفاده کرد:
 PM> Install-Package RefactorThis.GraphDiff


بررسی GraphDiff در طی یک مثال

مدل‌های برنامه آزمایشی، از سه کلاس ذیل که روابط many-to-many و one-to-many با یکدیگر دارند، تشکیل شده‌است:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace GraphDiffTests.Models
{
    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public virtual ICollection<Tag> Tags { set; get; } // many-to-many

        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public int UserId { get; set; }

        public BlogPost()
        {
            Tags = new List<Tag>();
        }
    }

    public class Tag
    {
        public int Id { set; get; }

        [StringLength(maximumLength: 450), Required]
        public string Name { set; get; }

        public virtual ICollection<BlogPost> BlogPosts { set; get; } // many-to-many

        public Tag()
        {
            BlogPosts = new List<BlogPost>();
        }
    }

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; } // one-to-many
    }
}
- یک مطلب می‌تواند چندین برچسب داشته باشد و هر برچسب می‌تواند به چندین مطلب انتساب داده شود.
- هر کاربر می‌تواند چندین مطلب ارسال کند.

در این حالت، Context برنامه چنین شکلی را خواهد یافت:
using System;
using System.Data.Entity;
using GraphDiffTests.Models;

namespace GraphDiffTests.Config
{
    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<BlogPost> BlogPosts { get; set; }
        public DbSet<Tag> Tags { get; set; }


        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }
    }
}
به همراه تنظیمات به روز رسانی ساختار بانک اطلاعاتی به صورت خودکار:
using System.Data.Entity.Migrations;
using System.Linq;
using GraphDiffTests.Models;

namespace GraphDiffTests.Config
{
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            if(context.Users.Any())
                return;

            var user1 = new User {Name = "User 1"};
            context.Users.Add(user1);

            var tag1 = new Tag { Name = "Tag1" };
            context.Tags.Add(tag1);

            var post1 = new BlogPost { Title = "Title...1", Content = "Content...1", User = user1};
            context.BlogPosts.Add(post1);

            post1.Tags.Add(tag1);

            base.Seed(context);
        }
    }
}
در متد Seed آن یک سری اطلاعات ابتدایی ثبت شده‌اند؛ یک کاربر، یک برچسب و یک مطلب.




در این تصاویر به Id هر کدام از رکوردها دقت کنید. از آن‌ها در ادامه استفاده خواهیم کرد.
در اینجا نمونه‌ای از نحوه‌ی استفاده از GraphDiff را جهت به روز رسانی یک Aggregate root ملاحظه می‌کنید:
            using (var context = new MyContext())
            {
                var user1 = new User { Id = 1, Name = "User 1_1_1" };
                var post1 = new BlogPost { Id = 1, Title = "Title...1_1", Content = "Body...1_1",
                    User = user1, UserId = user1.Id };
                var tags = new List<Tag>
                {
                    new Tag {Id = 1, Name = "Tag1_1"},
                    new Tag {Id=12, Name = "Tag2_1"},
                    new Tag {Name = "Tag3"},
                    new Tag {Name = "Tag4"},
                };
                tags.ForEach(tag => post1.Tags.Add(tag));

                context.UpdateGraph(post1, map => map
                    .OwnedEntity(p => p.User)
                    .OwnedCollection(p => p.Tags)
                    );

                context.SaveChanges();
            }
پارامتر اول UpdateGraph، گرافی از اشیاء است که قرار است به روز رسانی شوند.
پارامتر دوم آن، همان مباحث Owned و Associated بحث شده در ابتدای مطلب را مشخص می‌کنند. در اینجا چون می‌خواهیم هم برچسب‌ها و هم اطلاعات کاربر مطلب اول به روز شوند، نوع رابطه را Owned تعریف کرده‌ایم.
در حین کار با متد UpdateGraph، ذکر Idهای اشیاء منقطع از Context بسیار مهم هستند. اگر دستورات فوق را اجرا کنیم به خروجی ذیل خواهیم رسید:




- همانطور که مشخص است، چون id کاربر ذکر شده و همچنین این Id در post1 نیز درج گردیده است، صرفا نام او ویرایش گردیده است. اگر یکی از موارد ذکر شده رعایت نشوند، ابتدا کاربر جدیدی ثبت شده و سپس رابطه‌ی مطلب و کاربر به روز رسانی خواهد شد (userId آن به userId آخرین کاربر ثبت شده تنظیم می‌شود).
- در حین ثبت برچسب‌ها، چون Id=1 از پیش در بانک اطلاعاتی موجود بوده، تنها نام آن ویرایش شده‌است. در سایر موارد، برچسب‌های تعریف شده صرفا اضافه شده‌اند (چون Id مشخصی ندارند یا Id=12 در بانک اطلاعاتی وجود خارجی ندارد).
- چون Id مطلب مشخص شده‌است، فیلدهای عنوان و محتوای آن نیز به صورت خودکار ویرایش شده‌اند.

و ... تمام این کارها صرفا با فراخوانی متدهای UpdateGraph و سپس SaveChanges رخ داده‌است.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
GraphDiffTests.zip
مطالب
دریافت فایل از یوتیوب

سلام

سال نو مبارک! به امید سالی بهتر از پارسال!

این روزها با هزینه‌‌ای معادل هزینه‌ی تهیه‌ی یک هاست اشتراکی سالیانه برای بالاگذاری یک سایت معمولی در 5 سال قبل، می‌توان یک VPS تهیه کرد و به این صورت قفل و کلید یک نیمچه سرور را (با 200 و خرده‌ای مگ رم، 30 گیگ فضا، سرعت CPU نزدیک به 700 MHz و ویندوز سرور 2003 یا 2008) در اختیار شما قرار می‌دهند (البته به قول معروف هر چقدر پول بدهید همانقدر هم سخت افزار در اختیار شما قرار می‌دهند) بجای صرفا یک دایرکتوری مجازی محدود با 100 مگ فضای هاست که هر احدی در آن هاست اشتراکی می‌تواند سر مبارک را اندکی چرخانده و تمام زندگی شما را مرور کند و غیره!
استفاده‌ی مفیدی هم که این VPS برای من داشته، ترنس لود کردن یک سری فایل است (با توجه به سرعت‌های نجومی دریافت فایل این سرورها). برای مثال دریافت فایل از یوتیوب و انتقال به یک هاست دیگر برای دریافت ساده‌تر خودم و یا دیگران.
برای نمونه سایت dotnet-tv.com را در نظر بگیرید. تعدادی از ویدیوهای این سایت در یوتیوب هاست شده و از این دست زیاد هستند. خیلی‌ها برای فرار از مشکلات کمبود پهنای باند از یوتیوب استفاده می‌کنند. یوتیوب هم که از این طرف بسته است. خوب، من الان می‌خواهم ویدیوی مربوط به ASP.Net MVC آن را مشاهده کنم، چکار باید کرد؟!
یک برنامه‌ی ساده‌ی کنسول را تهیه کرده‌ام که این کار را برای VPS‌ داران تسهیل می‌کند.
- دریافت فایل از یوتیوب
- آپلود خودکار آن به رپیدشیر


یک نمونه خروجی آن: (فایل‌های یوتیوب سایت ذکر شده که به رپیدشیر منتقل شده)
دریافت

در سورس این برنامه موارد زیر پیاده سازی شده است:
- یافتن لینک‌های یوتیوب سایت dotnet-tv.com با استفاده از regular expressions
- یافتن لینک دانلود مستقیم این فایل‌ها از سایت یوتیوب که شامل استفاده از regular expressions برای استخراج قسمت‌های مفید از صفحات و همچنین استفاده از امکانات Json دات نت فریم ورک سه و نیم برای parse قسمت‌های استخراج شده است.
- ایجاد یک thread pool سفارشی که هر بار 7 لینک مستقیم را به صورت همزمان از یوتیوب دریافت می‌کند. (thread pool پیش فرض دات نت تمام تردها را به یکباره شروع می‌کند که برای اینکار مفید نیست. به همین جهت از این thread pool سفارشی شده استفاده شد)
پیش فرض فایلی که از سایت یوتیوب دریافت می‌شود MP4 با کیفیت بالا است که با fmt=18 در فایل Youtube.cs مشخص شده. فرمت‌های دیگر را می‌توانید از این فایل ایده بگیرید.
- آپلود فایل دریافتی از یوتیوب به یک اکانت رایگان کالکتور در رپیدشیر. (ماخذ این مورد در سایت code projects)
مشخصات این اکانت رایگان کالکتور در فایل app.config باید ذکر شود.

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



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

نظرات مطالب
T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
Reflection اندکی کند است. در حین توسعه برنامه اگر ویژوال استودیو مرتبا بخواهد کوئری‌های مبتنی بر reflection از اسمبلی‌های شما بگیرد، سرعت کار شما کم خواهد شد و نهایتا ممکن است سبب ناپایداری آن هم بشود (اگر پروژه جاری بزرگ باشد).
مطالب
بازنویسی سطح دوم کش برای Entity framework 6
چندی قبل مطلبی را در مورد پیاده سازی سطح دوم کش در EF در این سایت مطالعه کردید. اساس آن مقاله‌ای بود که نحوه‌ی کش کردن اطلاعات حاصل از LINQ to Objects را بیان کرده بود (^). این مقاله پایه‌ی بسیاری از سیستم‌های کش مشابه نیز شده‌است (^ و ^ و ...).
مشکل مهم این روش عدم سازگاری کامل آن با EF است. برای مثال در آن تفاوتی بین (Include(x=>x.Tags و (Include(x=>x.Users وجود ندارد. به همین جهت در این نوع موارد، قادر به تولید کلید منحصربفردی جهت کش کردن اطلاعات یک کوئری مشخص نیست. در اینجا یک کوئری LINQ، به معادل رشته‌ای آن تبدیل می‌شود و سپس Hash آن محاسبه می‌گردد. این هش، کلید ذخیره سازی اطلاعات حاصل از کوئری، در سیستم کش خواهد بود. زمانیکه دو کوئری Include دار متفاوت EF، هش‌های یکسانی را تولید کنند، عملا این سیستم کش، کارآیی خودش را از دست می‌دهد. برای رفع این مشکل پروژه‌ی دیگری به نام EF cache ارائه شده‌است. این پروژه بسیار عالی طراحی شده و می‌تواند جهت ایده دادن به تیم EF نیز بکار رود. اما در آن فرض بر این است که شما می‌خواهید کل سیستم را در یک کش قرار دهید. وارد مکانیزم DBCommand و DataReader می‌شود و در آن‌جا کار کش کردن تمام کوئری‌ها را انجام می‌دهد؛ مگر آنکه به آن اعلام کنید از کوئری‌های خاصی صرفنظر کند.
با توجه به این مشکلات، روش بهتری برای تولید هش یک کوئری LINQ to Entities بر اساس کوئری واقعی SQL تولید شده توسط EF، پیش از ارسال آن به بانک اطلاعاتی به صورت زیر وجود دارد:
        private static ObjectQuery TryGetObjectQuery<T>(IQueryable<T> source)
        {
            var dbQuery = source as DbQuery<T>;

            if (dbQuery != null)
            {
                const BindingFlags privateFieldFlags = 
                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public;

                var internalQuery =
                    source.GetType().GetProperty("InternalQuery", privateFieldFlags)
                        .GetValue(source);

                return
                    (ObjectQuery)internalQuery.GetType().GetProperty("ObjectQuery", privateFieldFlags)
                        .GetValue(internalQuery);
            }

            return null;
        }
این متد یک کوئری LINQ مخصوص EF را دریافت می‌کند و با کمک Reflection، اطلاعات درونی آن که شامل ObjectQuery اصلی است را استخراج می‌کند. سپس فراخوانی متد objectQuery.ToTraceString بر روی حاصل آن، سبب تولید SQL معادل کوئری LINQ اصلی می‌گردد. همچنین objectQuery امکان دسترسی به پارامترهای تنظیم شده‌ی کوئری را نیز میسر می‌کند. به این ترتیب می‌توان به معادل رشته‌ای منطقی‌تری از یک کوئری LINQ رسید که قابلیت تشخیص JOINها و متد Include نیز به صورت خودکار در آن لحاظ شده‌است.

این اطلاعات، پایه‌ی تهیه‌ی کتابخانه‌ی جدیدی به نام EFSecondLevelCache گردید. برای نصب آن کافی است دستور ذیل را در کنسول پاورشل نیوگت صادر کنید:
 PM> Install-Package EFSecondLevelCache
سپس برای کش کردن کوئری معمولی مانند:
 var products = context.Products.Include(x => x.Tags).FirstOrDefault();
می‌توان از متد جدید Cacheable آن به نحو ذیل استفاده کرد (این روش بسیار تمیزتر است از روش مقاله‌ی قبلی و امکان استفاده‌ی از انواع و اقسام متدهای EF را به صورت متداولی میسر می‌کند):
 var products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too.

پس از آن نیاز است کدهای کلاس Context خود را نیز به نحو ذیل ویرایش کنید (به روز رسانی شده‌ی آن در اینجا):
namespace EFSecondLevelCache.TestDataLayer.DataLayer
{
    public class SampleContext : DbContext
    {
        // public DbSet<Product> Products { get; set; }
 
        public SampleContext()
            : base("connectionString1")
        {
        }
 
        public override int SaveChanges()
        {
            return SaveAllChanges(invalidateCacheDependencies: true);
        }
 
        public int SaveAllChanges(bool invalidateCacheDependencies = true)
        {
            var changedEntityNames = getChangedEntityNames();
            var result = base.SaveChanges();
            if (invalidateCacheDependencies)
            {
               new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames);
            }
            return result;
        }
 
        private string[] getChangedEntityNames()
        {
            return this.ChangeTracker.Entries()
                .Where(x => x.State == EntityState.Added ||
                            x.State == EntityState.Modified ||
                            x.State == EntityState.Deleted)
                .Select(x => ObjectContext.GetObjectType(x.Entity.GetType()).FullName)
                .Distinct()
                .ToArray();
        }
    }
}
متد InvalidateCacheDependencies سبب می‌شود تا اگر تغییری در بانک اطلاعاتی رخ‌داد، به صورت خودکار کش‌های کوئری‌های مرتبط غیر معتبر شوند و برنامه اطلاعات قدیمی را از کش نخواند.


کدهای کامل این پروژه را از مخزن کد ذیل می‌توانید دریافت کنید:
EFSecondLevelCache



پ.ن.
این کتابخانه هم اکنون در سایت جاری در حال استفاده است.
مطالب
Angular Material 6x - قسمت هفتم - کار با انواع قالب‌ها
در این قسمت می‌خواهیم روش تغییر رنگ‌های قالب‌های پیش‌فرض Angular Material را به همراه تغییر پویای آن‌ها در زمان اجرا، بررسی کنیم. همچنین Angular Material از راست به چپ نیز به خوبی پشتیبانی می‌کند که مثالی از آن‌را در ادامه بررسی خواهیم کرد.


بررسی ساختار یک قالب Angular Material

قالب، مجموعه‌ای از رنگ‌ها است که به کامپوننت‌های Angular Material اعمال می‌شود. هر قالب از چندین جعبه‌رنگ یا palette تشکیل می‌شود:
- primary palette: به صورت گسترده‌ای در تمام کامپوننت‌ها مورد استفاده‌است.
- accent palette: به المان‌های تعاملی انتساب داده می‌شود.
- warn palette: برای نمایش خطاها و اخطارها بکار می‌رود.
- foreground palette: برای متون و آیکن‌ها استفاده می‌شود.
- background palette: برای پس‌زمینه‌ی المان‌ها بکار می‌رود.

روش انتخاب این جعبه رنگ‌ها نیز به صورت زیر است:
<mat-card>
  Main Theme:
  <button mat-raised-button color="primary">
    Primary
  </button>
  <button mat-raised-button color="accent">
    Accent
  </button>
  <button mat-raised-button color="warn">
    Warning
  </button>
</mat-card>
در Angular Material تمام قالب‌ها استاتیک بوده و در زمان کامپایل برنامه به صورت خودکار به آن اضافه می‌شوند. به همین جهت برنامه نیازی به تشکیل این اجزا و کامپایل یک قالب را در زمان آغاز آن ندارد.
همانطور که در قسمت اول این سری نیز بررسی کردیم، بسته‌ی Angular Material به همراه چندین قالب از پیش طراحی شده‌است (قالب‌های از پیش آماده‌ی متریال را در پوشه‌ی node_modules\@angular\material\prebuilt-themes می‌توانید مشاهده کنید) و در حین اجرای برنامه تنها یکی از آن‌ها که در فایل styles.css ذکر شده‌است، مورد استفاده قرار می‌گیرد.
اگر نیاز به سفارشی سازی بیشتری وجود داشته باشد، می‌توان قالب‌های ویژه‌ی خود را نیز طراحی کرد. این قالب جدید باید mat-core() sass mixin را import کند که حاوی تمام شیوه‌نامه‌های مشترک بین کامپوننت‌ها است. این مورد باید تنها یکبار به کل برنامه الحاق شود تا حجم آن‌را بیش از اندازه زیاد نکند. سپس این قالب سفارشی، جعبه رنگ‌های خاص خودش را معرفی می‌کند. در ادامه این جعبه رنگ‌ها توسط توابع mat-light-theme و یا mat-dark-theme ترکیب شده و مورد استفاده قرار می‌گیرند. سپس این قالب را include خواهیم کرد. به این ترتیب یک قالب سفارشی Angular Material، چنین طرحی را دارد:
@import '~@angular/material/theming';
@include mat-core();

$candy-app-primary: mat-palette($mat-indigo);
$candy-app-accent: mat-palette($mat-pink, A200, A100, A400);
$candy-app-warn: mat-palette($mat-red);

$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);

@include angular-material-theme($candy-app-theme);
اعداد و ارقامی را که در اینجا ملاحظه می‌کنید، در سیستم رنگ‌های طراحی متریال به darker hue و lighter hue تفسیر می‌شوند. همچنین امکان سفارشی سازی تایپوگرافی آن نیز وجود دارد.
ذکر جعبه رنگ اخطار در اینجا اختیاری است و اگر ذکر نشود به قرمز تنظیم خواهد شد.

ایجاد یک قالب سفارشی جدید Angular Material

برای ایجاد یک قالب سفارشی نیاز است از فایل‌های sass استفاده کرد. بنابراین بهترین روش ایجاد برنامه‌های Angular Material در ابتدای کار، ذکر صریح نوع style مورد استفاده به sass است:
 ng new MyProjectName --style=sass
اگر اینکار را انجام نداده‌ایم و حالت پیش‌فرض پروژه همان css است، مهم نیست. می‌توان فایل قالب سفارشی را در یک فایل با پسوند custom.theme.scss نیز در پوشه‌ی src قرار داد و سپس آن‌را در فایل angular.json مشخص کرد تا به صورت css کامپایل شده و مورد استفاده قرار گیرد:
"styles": [
   "node_modules/material-design-icons/iconfont/material-icons.css",
   "src/styles.css",
   "src/custom.theme.scss"
],
بدیهی است اگر از ابتدا style=sass را تنظیم کرده بودیم، نیازی به ایجاد این فایل اضافی نبود و همان styles.scss اصلی را می‌شد ویرایش کرد و در این حالت فایل angular.json بدون تغییر باقی می‌ماند.
پس از افزودن و تنظیم فایل custom.theme.scss، به فایل styles.css مراجعه کرده و قالب فعلی را به صورت comment در می‌آوریم:
/* @import "~@angular/material/prebuilt-themes/indigo-pink.css"; */

body {
  margin: 0;
}
سپس فایل src\custom.theme.scss را به صورت زیر تکمیل می‌کنیم:
@import '~@angular/material/theming';
@include mat-core();

$my-app-primary: mat-palette($mat-blue-grey);
$my-app-accent:  mat-palette($mat-pink, 500, 900, A100);
$my-app-warn:    mat-palette($mat-deep-orange);

$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

@include angular-material-theme($my-app-theme);

.alternate-theme {
  $alternate-primary: mat-palette($mat-light-blue);
  $alternate-accent:  mat-palette($mat-yellow, 400);

  $alternate-theme: mat-light-theme($alternate-primary, $alternate-accent);

  @include angular-material-theme($alternate-theme);
}
که در اینجا شامل مراحل import فایل‌های پایه‌ی Angular Material، تعریف جعبه رنگ جدید، ترکیب آن‌ها و در نهایت include آن‌ها می‌باشد.

در اینجا روش تعریف یک قالب دوم (alternate-theme) را نیز مشاهده می‌کنید. علت تعریف قالب دوم در همین فایل جاری، کاهش حجم نهایی برنامه است. از این جهت که اگر alternate-themeها را در فایل‌های scss دیگری قرار دهیم، مجبور به import تعاریف اولیه‌ی قالب‌های Angular Material در هرکدام به صورت جداگانه‌ای خواهیم بود که حجم قابل ملاحظه‌ای را به خود اختصاص می‌دهند. به همین جهت قالب‌های دیگر را نیز در همینجا به صورت کلاس‌های ثانویه تعریف خواهیم کرد.
 در این حالت روش استفاده‌ی از این قالب ثانویه به صورت زیر می‌باشد:
<mat-card class="alternate-theme">
  Alternate Theme:
  <button mat-raised-button color="primary">
    Primary
  </button>
  <button mat-raised-button color="accent">
    Accent
  </button>
  <button mat-raised-button color="warn">
    Warning
  </button>
</mat-card>

پس از افزودن فایل src\custom.theme.scss به برنامه، اگر آن‌را اجرا کنیم به خروجی زیر خواهیم رسید:




افزودن امکان انتخاب پویای قالب‌ها به برنامه

قصد داریم به منوی برنامه که اکنون گزینه‌ی new contact را به همراه دارد، گزینه‌ی toggle theme را هم جهت تغییر پویای قالب اصلی برنامه اضافه کنیم. به همین جهت فایل toolbar.component.html را گشوده و به صورت زیر تغییر می‌دهیم:
  <mat-menu #menu="matMenu">
    <button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
    <button mat-menu-item (click)="toggleTheme.emit()">Toggle theme</button>
  </mat-menu>
در اینجا دکمه‌ای به منو اضافه شده‌است که سبب صدور رخدادی به والد آن یا همان sidenav خواهد شد. علت اینجا است که تغییر قالب را در sidenav، که در برگیرنده‌ی router-outlet است، می‌توان به کل برنامه اعمال کرد.
بنابراین جهت تبادل اطلاعات بین toolbar و sidenav از یک رخ‌داد استفاده خواهیم کرد. برای این منظور فایل toolbar.component.ts را گشوده و این رخ‌داد را به آن اضافه می‌کنیم:
export class ToolbarComponent implements OnInit {

  @Output() toggleTheme = new EventEmitter<void>();
پس از این تعریف، به sidenav.component.html مراجعه کرده و به این رخ‌داد گوش فرا می‌دهیم:
<app-toolbar (toggleTheme)="toggleTheme()" (toggleSidenav)="sidenav.toggle()"></app-toolbar>
متد toggleTheme را نیز به صورت زیر به sidenav.component.ts اضافه می‌کنیم:
export class SidenavComponent {

  isAlternateTheme = false;

  toggleTheme() {
    this.isAlternateTheme = !this.isAlternateTheme;
  }
}
در اینجا یک خاصیت عمومی boolean را با کلیک بر روی گزینه‌ی منوی Toggle theme، به true و یا false تنظیم می‌کنیم. اکنون از این مقدار جهت تغییر css قالب sidenav استفاده خواهیم کرد:
<mat-sidenav-container fxLayout="row" class="app-sidenav-container" fxFill 
   [class.alternate-theme]="isAlternateTheme">
به این ترتیب اگر مقدار isAlternateTheme مساوی true باشد، کلاس alternate-theme به قالب sidenav به صورت پویا اعمال خواهد شد و برعکس. در تصویر زیر نمونه‌ای از تغییر پویای قالب برنامه را مشاهده می‌کنید:




افزودن پشتیبانی از راست به چپ به قالب برنامه

اگر به mat-sidenav-container ویژگی dir=rtl را اضافه کنیم، قالب برنامه راست به چپ خواهد شد. در ادامه می‌خواهیم شبیه به حالت تغییر پویای قالب سایت، گزینه‌ای را به منوی برنامه جهت تغییر جهت برنامه نیز اضافه کنیم. برای این منظور به قالب toolbar.component.html مراجعه کرده و گزینه‌ی Toggle dir را به آن اضافه می‌کنیم:
  <mat-menu #menu="matMenu">
    <button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
    <button mat-menu-item (click)="toggleTheme.emit()">Toggle theme</button>
    <button mat-menu-item (click)="toggleDir.emit()">Toggle dir</button>
  </mat-menu>
سپس این رخ‌داد را که قرار است در نهایت به sidenav منتقل شود، به صورت زیر به toolbar.component.ts اضافه می‌کنیم:
export class ToolbarComponent implements OnInit {

  @Output() toggleDir = new EventEmitter<void>();
اکنون در sidenav.component.html به این ر‌خ‌داد گوش فرا خواهیم داد:
<app-toolbar (toggleDir)="toggleDir()" 
(toggleTheme)="toggleTheme()" 
(toggleSidenav)="sidenav.toggle()"></app-toolbar>
متد toggleDir در sidenav.component.ts به صورت زیر پیاده سازی می‌شود:
export class SidenavComponent implements OnInit, OnDestroy {

  dir = "ltr";

  toggleDir() {
    this.dir = this.dir === "ltr" ? "rtl" : "ltr";
  }
}
و در نهایت این جهت را به mat-sidenav-container در فایل sidenav.component.html اعمال می‌کنیم:
<mat-sidenav-container fxLayout="row" class="app-sidenav-container" fxFill [dir]="dir"
  [class.alternate-theme]="isAlternateTheme">
در تصویر زیر نمونه‌ای از تغییر پویای جهت برنامه را مشاهده می‌کنید:




کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-06.zip
برای اجرای آن:
الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
مطالب
OpenCVSharp #1
معرفی OpenCV

پردازش تصاویر علمی است برای پیاده سازی الگوریتم‌های مختلفی بر روی تصاویر دیجیتال؛ برای مثال تشخیص خودکار شماره‌ی پلاک خودروهای وارد شده‌ی به محدوده‌ی طرح ترافیک، تا تشخیص چهره‌ی افراد، در گوشی‌های همراه. پردازش تصاویر، در صنایع مختلف، علوم پزشکی و همچنین نظامی، کاربردهای بسیاری دارند.
برای انجام این کار، کتابخانه‌های بسیار زیادی طراحی شده‌اند؛ اما در این بین OpenCV جایگاه خاصی دارد. این کتابخانه‌ی بسیار مشهور سورس باز، جهت پردازش تصاویر در سیستم عامل‌های مختلفی مانند Windows, Mac, Linux, Android و iOS بکار می‌رود.


محصور کننده‌های OpenCV مخصوص دات نت

تا امروز محصور کننده‌های زیادی جهت استفاده‌ی از کتابخانه‌ی OpenCV در دات نت طراحی شده‌اند که تعدادی از مهم‌ترین‌های آن‌ها به شرح زیر هستند:

الف) Emgu CV
این کتابخانه، یکی از مشهورترین محصور کننده‌های OpenCV است و دارای مجوزی دوگانه می‌باشد. برای کارهای سورس باز، مجوز GPL دارد (یعنی باید کارتان را سورس باز کنید) و برای کارهای تجاری باید مجوز آن‌را بخرید. البته باید توجه داشت که مجوز کتابخانه‌ی اصلی OpenCV از نوع BSD است و این محدودیت‌ها را ندارد.

ب) OpenCvSharp
کتابخانه‌ی OpenCvSharp دارای مجوز BSD است (همانند کتابخانه‌ی اصلی OpenCV) و محدودیتی برای استفاده ندارد. هر دو نوع مدل برنامه نویسی OpenCV را که شامل متدهای C و ++C آن‌است، پشتیبانی می‌کند و در طراحی آن سعی شده‌است که بیشترین نزدیکی به طراحی اصلی OpenCV وجود داشته باشد. همچنین این کتابخانه چندسکویی بوده و با Mono لینوکسی نیز سازگار است و از دات نت 2 به بعد را نیز پشتیبانی می‌کند. جامعه‌ی کاربری آن فعال است و مدام به روز می‌شود.

ج) SharperCV
دیگر نگهداری نمی‌شود.

د) OpenCVDotNet 
آخرین تاریخ به روز رسانی آن سال 2007 است.

ه) DirectCV
آخرین تاریخ به روز رسانی آن سال 2011 است.


در این بین یکی از بهترین انتخاب‌ها، کتابخانه‌ی OpenCvSharp ژاپنی است. مجوز استفاده‌ی از آن محدود نیست. به روز رسانی مرتب و منظمی دارد و API آن طوری طراحی شده‌است که به سادگی بتوانید مثال‌های C و ++C کتابخانه‌ی OpenCV را تبدیل به معادل‌های #C کنید.


نصب OpenCvSharp

برای نصب کتابخانه‌ی OpenCvSharp می‌توان از بسته‌های نیوگت آن کمک گرفت. این کتابخانه به همراه دو بسته‌ی نیوگت ارائه می‌شود.
اگر فرمان ذیل را صادر کنید
 PM> Install-Package OpenCvSharp-AnyCPU
علاوه بر اسمبلی‌های دات نتی OpenCVSharp، کتابخانه‌ی native مربوط به OpenCV سازگار با نگارش ارائه شده را نیز دریافت خواهید کرد.
و اگر دستور ذیل را اجرا کنید:
 PM> Install-Package OpenCvSharp-WithoutDll
به این معنا است که تنها اسمبلی‌های دات نتی OpenCVSharp را دریافت می‌کنید. در این حالت نیاز است به سایت OpenCV مراجعه و بسته‌های کامپایل شده‌ی آن‌را دریافت کنید. سپس فایل‌های dll موجود در پوشه‌ی opencv\build\x64\vc12\bin را برای مثال به پوشه‌ی bin پروژه‌ی خود کپی نمائید.

روش توصیه شده‌ی در اینجا، همان نصب بسته‌ی نیوگت OpenCvSharp-AnyCPU است. به این ترتیب نگارش‌های X86 و X64 کتابخانه‌ی OpenCV سازگار با OpenCvSharp را نیز دریافت خواهید کرد.


نکته‌ای در مورد ارائه‌ی نهایی پروژه‌های مبتنی بر OpenCV

OpenCV یک کتابخانه‌ی native ویندوز است و دات نتی نیست . بنابراین DLL‌های آن باید بسته به معماری CPU جاری، انتخاب شوند. یعنی اگر برنامه‌ی دات نتی خود را در حالت Any CPU کامپایل می‌کنید، این برنامه در یک سیستم 64 بیتی، 64 بیتی رفتار می‌کند و در یک سیستم 32 بیتی، 32 بیتی. بنابراین باید دقت داشت که اگر سیستم جاری 64 بیتی است و می‌خواهید از اسمبلی‌های X86 مربوط به OpenCV استفاده کنید، برنامه با پیام استثنای یافت نشدن OpenCV و BadImageFormatException کرش خواهد کرد. بسته‌ی نیوگت OpenCvSharp-AnyCPU  شامل هر دو معماری X86 و X64 است و هر دو سری DLLهای OpenCV را به همراه دارد.
همچنین OpenCV تحت ویندوز، توسط کامپایلر ویژوال ++C، کامپایل شده‌است. به همین جهت در این حالت، علاوه بر نصب دات نت، نیاز است VC++ redistributable packages را نیز بر روی کامپیوتر کلاینت نصب کرد.
پس از نصب بسته‌ی نیوگت OpenCvSharp-AnyCPU اگر به پوشه‌ی bin برنامه‌ی خود مراجعه کنید، پوشه‌ی جدید dll را نیز می‌توان مشاهده کرد. داخل این پوشه، دو پوشه‌ی X86 و X64 وجود دارند که حاوی DLLهای اصلی OpenCV می‌باشند. در این پوشه‌ها اگر برای مثال فایلی به نام msvcp120.dll را یافتید، یعنی این نگارش از OpenCV نیاز به بسته‌های مخصوص VC++ 12 دارد.

رعایت این دو نکته بسیار مهم است؛ در غیر اینصورت برنامه‌ی شما آغاز نخواهد شد.


اولین برنامه‌ی OpenCVSharp


پس از نصب بسته‌ی نیوگت OpenCvSharp-AnyCPU، مقدمات نصب OpenCV به پایان می‌رسد. در ادامه یک برنامه‌ی کنسول جدید را ایجاد کرده و کدهای ذیل را به آن اضافه کنید:
using OpenCvSharp;
 
namespace OpenCVSharpSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            var img = Cv.CreateImage(new CvSize(128, 128), BitDepth.U8, 1);
 
            for (var y = 0; y < img.Height; y++)
            {
                for (var x = 0; x < img.Width; x++)
                {
                    Cv.Set2D(img, y, x, x + y);
                }
            }
 
            Cv.NamedWindow("window");
            Cv.ShowImage("window", img);
            Cv.WaitKey();
            Cv.DestroyWindow("window");
 
            Cv.ReleaseImage(img);
        }
    }
}
این خروجی را دریافت خواهید کرد:


در این مثال یک تصویر 128*128 ایجاد شده و سپس با گرادیانی از رنگ خاکستری پر می‌شود. در ادامه یک پنجره‌ی native مخصوص OpenCV ایجاد شده و این تصویر در آن نمایش داده می‌شود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.