مطالب
Asp.Net Identity #1
API ، Identity جدید مایکروسافت جهت مدیریت کاربران در برنامه‌های ASP.NET می‌باشد. نقطه‌ی اتکای مایکروسافت در سالهای اخیر برای مدیریت کاربران سیستم ASP.NET Membership بود که از ضعف‌های طراحی رنج می‌برد. مهمترین محدودیت این سیستم این بود که داده‌های ذخیره شده توسط Schema، فقط قابلیت کار با SQL Server را دارا بود که توسعه‌ی آن بدون پیاده سازی دوباره‌ی کلاسهای تامین کننده ( Provider Classes ) بسیار مشکل بود. بعد از آن مایکروسافت جهت کاهش پیچیدگی سیستم ASEP.NET Membership، دو سیستم دیگر به نامهای Simple Membership و ASP.NET Universal Providers را عرضه نمود. این سیستم‌ها تا حدودی پیچیدگی را کم کردند، اما همچنان برمبنای SQL Server بودند. تا اینکه مایکروسافت سیستم ASP.NET Membership را با سیستم Identity جهت حل دو مشکل مطرح شده تعویض نمود. سیستم Identity هم پایدار است و هم قابل توسعه. به علاوه با استفاده از قابلیت Open Source بودن، قابل وفق دادن می‌باشد (یعنی برحسب نیاز پروژه می‌توان کل یا قسمتهایی را پیاده سازی نمود). برای بسیاری از توسعه دهندگان برنامه‌های ASP.NET، سیستم Identity اولین پرده برداری از  OWIN خواهد بود. در حقیقت OWIN یک لایه‌ی Abstract می‌باشد که برنامه‌ی وب را از محیطی که آن‌را میزبانی کرده، ایزوله می‌کند. هدف از ایزوله کردن، ایجاد انعطاف پذیری بیشتر در محیط هایی که برنامه‌های ASP.Net را میزبانی می‌کنند و همچنین ایجاد یک زیرساخت سبک وزن جهت سرویس دهی می‌باشد. همان طور که عنوان شد OWIN یک استاندارد باز است و مایکروسافت پروژه KATANA را به منظور پیاده سازی استانداردی از OWIN طراحی کرد. این طراحی همراه با یک سری کامپوننت به منظور تامین ویژگی‌هایی (Functionalities) که یک برنامه وب به آنها نیاز دارد همراه بود. زیبایی کار مایکروسافت در این بود که OWIN/KATANA پشته‌ی تکنولوژی ASP.NET را از بقیه‌ی قسمتهای .NET Framework ایزوله کرده که اجازه نرخ بالاتری از تغییرات را می‌دهد.
توسعه دهندگان OWIN به جای استفاده از کل پلتفرم (بر عکس چیزی که در حال حاضر در سرویسهای ASP.NET رخ می‌دهد) فقط سرویس‌هایی را که برنامه آنها نیاز دارد، انتخاب می‌کنند. این سرویسهای منفرد که در واژه نامه‌ی OWIN با نام Middleware شناخته می‌شوند، می‌توانند در نرخ‌های مختلفی توسعه داده شوند و توسعه دهندگان را قادر می‌سازند که بین تأمین کنندگان سرویسهای مختلف یکی را انتخاب کنند و فقط وابسته به سرویس‌های پیاده سازی شده توسط مایکروسافت نباشند.
و در پایان باید گفت که پلتفرم ASP.NET و IIS کنار گذاشته نخواهند شد. مایکروسافت شفاف سازی کرده‌است که یکی از ابعاد زیبای OWIN این است که به توسعه دهندگان اجازه انعطاف پذیری بیشتر را می‌دهد که در آن کامپوننت‌های Middleware بوسیله IIS میزبانی می‌شوند و هم اکنون پروژه‌ی KATANA توسط فضاهای نام System.Web پشتیبانی می‌شود. 
خوب دوستان این مقدمه ای بود بر سیستم Identity . انشالله در نوشته‌های بعدی بیشتر سیستم Identity را تحلیل و بررسی خواهیم کرد. 
مطالب
زیر نویس فارسی ویدیوهای ساخت برنامه‌های مترو توسط سی شارپ و XAML - قسمت اول

سایت pluralsight ویدیوهای آموزشی بسیار با کیفیتی را در مورد مباحث مختلف دات نت تا بحال تهیه کرده و تقریبا هر موضوع جدیدی هم که اضافه می‌شود، بلافاصله یک سری جدید را تهیه می‌کنند. مدرسین انتخابی هم عموما افراد نامدار و باسوادی هستند.
پروژه‌ای رو در سایت کدپلکس شروع کردم جهت تهیه زیرنویس فارسی برای این ویدیوها:


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

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

سری جدیدی رو که شروع کردم تحت عنوان «Building Windows 8 Metro Apps in C# and XAML» در سایت pluralsight ارائه شده.
فعلا قسمت اول آن زیرنویس دار شده و از اینجا قابل دریافت است. برای مشاهده آن‌ها برنامه با کیفیت و رایگان KMPlayer توصیه می‌شود.

لیست ویدیوهای قسمت اول آن به شرح زیر است:

Building Windows 8 Metro Apps in C# and XAML 
Overview 00:50:41
This modules provides an overview of how to develop Windows 8 Metro style applications in C# and XAML.

Introduction
XAML and Codebehind
Asynchronous APIs
Demo: Asynchronous APIs
Files and Networking
Demo: Requesting Capabilities
Integrating with Windows 8
WinRT and .NET
Summary

کلمه سی شارپ در این قسمت کمی غلط انداز است. بیشتر بحث و توضیح است تا کد نویسی. بنابراین برای عموم قابل استفاده است. خصوصا نگاهی دارد به تازه‌های ویندوز 8 از دیدگاه برنامه نویس‌ها مانند سطوح دسترسی برنامه‌های مترو، معرفی Charms ، نحوه به اشتراک گذاری اطلاعات در بین برنامه‌های مترو نصب شده، برای مثال جایی که دیگر Clipboard سابق وجود ندارد و مواردی از این دست.

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

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

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

در مورد این که زمانی طول میکشه این تبدیل کدهای sdk توسط تیم زمرین انجام بشه مطمئنا وجود داره ولی با یک نگاه به این مسئله که api‌های سطح جدید در واقعیت دنیای ما مدتی طول میکشه تا جا باز کنه و مطمئنا تا قبل از اون موقع تیم زمرین کار رو تموم کرده. نوشتن برنامه ای با سطح بالای api‌ها در همان لحظه برابر است با نداشتن کاربری که این نسخه از اندروید را روی گوشی اش داشته باشد و بعد از آن هم تعداد اندکی از کاربران خواهند بود که این نسخه را دارند پس با این حساب این مسئله تا مدت‌ها منتفی است.
مطالب
React 16x - قسمت 34 - توزیع برنامه
در قسمت آخر این سری، نگاهی خواهیم داشت به نحوه‌ی توزیع برنامه‌های React و نکات مرتبط با آن.


افزودن متغیرهای محیطی

در برنامه‌ی نمایش لیست فیلم‌هایی که تا قسمت 29 آن‌را بررسی کردیم، از فایل src\config.json برای ذخیره سازی اطلاعات تنظیمات برنامه استفاده شد. هرچند این روش کار می‌کند اما بر اساس محیط‌های مختلف توسعه، متغیر نیست. اغلب برنامه‌ها باید بتوانند حداقل در سه محیط توسعه، آزمایش و تولید، بر اساس متغیرها و تنظیمات خاص هر کدام، کار کنند. برای مثال بر روی سیستمی که کار توسعه در آن انجام می‌شود، می‌خواهیم apiUrl متفاوتی را نسبت به حالتیکه برنامه توزیع می‌شود، داشته باشیم.
برای رفع این مشکل، برنامه‌هایی که توسط create-react-app تولید می‌شوند، دارای پشتیبانی توکاری از متغیرهای محیطی هستند. برای این منظور نیاز است در ریشه‌ی پروژه (جائیکه فایل package.json قرار دارد) فایل جدید env. را ایجاد کرد. در ویندوز برای ایجاد یک چنین فایل‌هایی که فقط از یک پسوند تشکیل می‌شوند، باید نام فایل را به صورت .env. وارد کرد؛ سپس خود ویندوز نقطه‌ی نهایی را حذف می‌کند. البته اگر از ادیتور VSCode برای ایجاد این فایل استفاده می‌کنید، نیازی به درج نقطه‌ی انتهایی نیست. در این فایل environment ایجاد شده می‌توان تمام متغیرهای محیطی مورد نیاز را با مقادیر پیش‌فرض آن‌ها درج کرد. همچنین می‌توان این مقادیر پیش‌فرض را بر اساس محیط‌های مختلف کاری، بازنویسی کرد. برای مثال می‌توان فایل env.development. را اضافه کرد؛ به همراه فایل‌های env.test. و env.production.


متغیرهای محیطی به صورت key=value درج می‌شوند. این کلیدها نیر باید با REACT_APP_ شروع شوند؛ در غیر اینصورت، کار نخواهند کرد. برای مثال در فایل env.، دو متغیر پیش‌فرض زیر را تعریف می‌کنیم:
REACT_APP_NAME=My App
REACT_APP_VERSION=1
اکنون برای خواندن این متغیرها برای مثال در فایل index.js (و یا هر فایل جاوا اسکریپتی دیگری در برنامه)، سطر زیر را درج می‌کنیم:
console.log(process.env);
process به معنای پروسه‌ی جاری برنامه‌است (و مرتبط است به پروسه‌ی node.js ای که برنامه‌ی React را اجرا می‌کند) و خاصیت env، به همراه تمام متغیرهای محیطی برنامه می‌باشد. در این حالت اگر برنامه را اجرا کنیم، در کنسول توسعه دهندگان مرورگر، به یک چنین خروجی خواهیم رسید:


در این خروجی، متغیر "NODE_ENV: "development به صورت خودکار با تولید بسته‌های مخصوص ارائه‌ی نهایی، به production تنظیم می‌شود. سایر متغیرهای محیطی تعریف شده را نیز در اینجا ملاحظه می‌کنید. با توجه به خواص شیء env، برای مثال جهت دسترسی به نام برنامه می‌توان از مقدار process.env.REACT_APP_NAME استفاده کرد.


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


نحوه‌ی پردازش متغیرهای محیطی درج شده‌ی در برنامه

اگر همان سطر لاگ کردن خروجی process.env را به صورت زیر تغییر دهیم:
console.log("My App Name", process.env.REACT_APP_NAME);
و برنامه را مجددا اجرا کنیم، با مراجعه‌ی به برگه‌ی Sources و انتخاب مسیر localhost:3000/static/js/main.chunk.js و سپس جستجوی "My App Name" ای که در اینجا اضافه کردیم (با فشردن دکمه‌های Ctrl+F)، به خروجی زیر خواهیم رسید:


همانطور که مشاهده می‌کنید، فراخوانی console.log ما، دیگر به همراه متغیر process.env.REACT_APP_NAME نیست؛ بلکه مقدار اصلی این متغیر در اینجا درج شده‌است. بنابراین اگر در در حین توسعه‌ی برنامه، از متغیرهای محیطی استفاده شود، این متغیرها با مقادیر اصلی آن‌ها در حین پروسه‌ی Build نهایی، جایگزین می‌شوند.


Build برنامه‌های React برای محیط تولید

اجرای دستور npm start، سبب ایجاد یک Build مخصوص محیط توسعه می‌شود که بهینه سازی نشده‌است و به همراه اطلاعات اضافی قابل توجهی جهت دیباگ ساده‌تر برنامه‌است. برای رسیدن به یک خروجی بهینه سازی شده‌ی مخصوص محیط تولید و ارائه‌ی نهایی باید دستور npm run build را در خط فرمان اجرا کرد. خروجی نهایی این دستور، در پوشه‌ی جدید build واقع در ریشه‌ی پروژه، قرار می‌گیرد. اکنون می‌توان کل محتویات این پوشه را جهت ارائه‌ی نهایی در وب سرور خود، مورد استفاده قرار داد.
پس از پایان اجرای دستور npm run build، پیام «امکان ارائه‌ی آن توسط static server زیر نیز وجود دارد» ظاهر می‌شود:
> npm install -g serve
> serve -s build
اگر علاقمند باشید تا خروجی حالت production تولید شده را نیز به صورت محلی آزمایش کنید، ابتدا باید static server یاد شده را توسط دستور npm install فوق نصب کنید. سپس ریشه‌ی پروژه را در خط فرمان باز کرده و دستور serve -s build را صادر کنید (البته اگر با خط فرمان به پوشه‌ی build وارد شدید، دیگر نیازی به ذکر پوشه‌ی build نخواهد بود). اکنون می‌توانید برنامه را در آدرس http://localhost:5000 در مرورگر خود بررسی نمائید.

البته با توجه به اینکه backend سرور برنامه‌های ما نیز در همین آدرس قرار دارد و در صورت ورود این آدرس، به صورت خودکار به https://localhost:5001/index.html هدایت خواهید شد، می‌توان این پورت پیش‌فرض را با اجرای دستور  serve -s build -l 1234 تغییر داد. اکنون می‌توان آدرس جدید http://localhost:1234 را در مرورگر آزمایش کرد که ... با خطای زیر کار نمی‌کند:
Access to XMLHttpRequest at 'https://localhost:5001/api/genres' from origin 'http://localhost:1234' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
روش رفع این مشکل را در قسمت 23 بررسی کردیم و در اینجا جهت بهبود آن می‌توان متد WithOrigins فایل Startup.cs را به صورت زیر تکمیل کرد:
WithOrigins("http://localhost:3000", "http://localhost:1234")

یک نکته: زمانیکه از دستور npm start استفاده می‌شود، متغیرهای محیطی از فایل env.development. خوانده خواهند شد و زمانیکه از دستور npm run build استفاده می‌شود، این متغیرها از فایل env.production. تامین می‌شوند. در این حالت‌ها اگر متغیری در این دو فایل درج نشده بود، از مقدار پیش‌فرض موجود در فایل env. استفاده می‌گردد. از فایل env.test. با اجرای دستور npm test، به صورت خودکار استفاده می‌شود.


آماده سازی برنامه‌ی React، برای توزیع نهایی

تا اینجا برنامه‌ی React تهیه شده، اطلاعات apiUrl خودش را از فایل config.json دریافت می‌کند. اکنون می‌خواهیم بر اساس حالات مختلف توسعه و تولید، از apiUrlهای متفاوتی استفاده شود. به همین جهت به فایل env.production. مراجعه کرده و تنظیمات ذیل را به آن اضافه می‌کنیم:
REACT_APP_API_URL=https://localhost:5001/api
REACT_APP_ADMIN_ROLE_NAME=Admin
البته فعلا همین متغیرها را به فایل env.development. نیز می‌توان اضافه کرد؛ چون backend سرور ما در هر دو حالت، در این مثال، در آدرس فوق قرار دارد.

اکنون به برنامه مراجعه کرده و در هرجائی که ارجاعی به فایل config.json وجود دارد، سطر import آن‌را حذف می‌کنیم. با این تغییر، تمام آدرس‌هایی مانند:
const apiEndpoint = apiUrl + "/users";
را به صورت زیر ویرایش می‌کنیم:
const apiEndpoint =  "/users";
در ادامه برای تامین مقدار apiUrl به صورت خودکار، به فایل src\services\httpService.js مراجعه کرده و در ابتدای آن، یک سطر زیر را اضافه می‌کنیم:
 axios.defaults.baseURL = process.env.REACT_APP_API_URL;
به این ترتیب تمام درخواست‌های ارسالی توسط Axios، دارای baseURL ای خواهند شد که از فایل متغیر محیطی جاری تامین می‌شود. همانطور که پیش‌تر نیز عنوان شد، این مقدار در زمان Build، با مقدار ثابتی که از فایل env جاری خوانده می‌شود، جایگزین خواهد شد.
همچنین adminRoleName مورد نیاز در فایل src\services\authService.js را نیز از همان فایل env جاری تامین می‌کنیم:
const adminRoleName =  process.env.REACT_APP_ADMIN_ROLE_NAME;
پس از این تغییرات، نیاز است برای حالت توسعه، یکبار دیگر دستور npm start و یا برای حالت تولید، دستور npm run build را اجرا کرد تا اطلاعات درج شده‌ی در فایل‌های env.، پردازش و جایگزین شوند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-34-frontend.zip و sample-34-backend.zip
مطالب
آشنایی با الگوی M-V-VM‌ - قسمت سوم

در این قسمت، WPF MVVM Toolkit مایکروسافت به صورت کامل بررسی شده است (دریافت، نصب، ارائه یک مثال به همراه توضیحات و ایجاد آزمون‌های واحد).

فهرست مطالب:
فصل 3- آشنایی با WPF MVVM Toolkit
  • مقدمه
  • نصب WPF Model-View-ViewModel Toolkit
  • معرفی برنامه‌ی فصل
  • داده‌های برنامه
  • مدل برنامه
  • ViewModel برنامه
  • View برنامه
  • افزودن Command به برنامه
  • ایجاد آزمون‌های واحد

دریافت قسمت سوم
دریافت مثال قسمت سوم

مطالب
آشنایی با قابلیت جدید ASP.NET Web Forms Scaffolding
مایکروسافت با افزایش سرعت به روز رسانی توسعه پروژه‌های سورس باز خود جهت پاسخ دادن به نیاز توسعه دهندگان و توسعه ویژوال استادیو مطابق با آخرین تکنولوژی‌های تولید وب سایت، می‌کوشد تعداد بیشتری از توسعه دهندگان را به سمت استفاده از تکنولوژی‌های خود سوق دهد. 

سالها است که برنامه نویسان خبره با توجه به روش کاری خود از امکانات Code Generatorها برای تولید کدهای لایه‌های Data Access ، Logic و یا حتی User Interface استفاده می‌نمایند. پس از عرضه Entity Framework و تولید خودکار کدهای لایه های Data Access و Logic، این بار این امکان علاوه بر ASP.NET MVC در ASP.NET Web Forms نیز فراهم گردیده‌است تا بدون کد نویسی خسته کننده و تکراری، کدهای لایه رابط کاربر (Create-Read-Update-Delete (CRUD را نیز تولید نماییم. 

شروع کار با ASP.NET Scaffolding
پیش نیاز این کار استفاده از Visual Studio 2012 به همراه Web Tools 2012.2 می‌باشد.
  1. اول، ابزار Microsoft ASP.NET Scaffolding را از منوی Tools گزینه Extensions and Updates دریافت و نصب نمایید.
  2. دوم پروژه جدیدی از نوع Visual C# ASP.NET Web Forms Application با فریم ورک 4.5 ایجاد نمایید.
  3. از پنجره NuGet Package manager با دستور install کتابخانه ASP.NET Web Forms Scaffold Generator را دریافت نمایید
    install-package Microsoft.AspNet.Scaffolding.WebForms -pre
  4. کلاس Person را مانند زیر در فولدر Models ایحاد نمایید
     public class Person
        {
            [ScaffoldColumn(false)]
            public int ID { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }
    ویژگی ScaffoldColumn را برای ID، برابر false قرار دهید تا از ایجاد این ستون جلوگیری نمائید.
  5. پروژه را Build نمایید.
  6. بر روی پروژه راست کلیک و از گزینه Add، گزینه ...Scaffold را انتخاب نمایید.

  7. از پنجره Add Scaffold باز شده بر روی گزینه Add، کلیک کنید.

  8. پنجره  Add Web Forms Pages مانند زیر باز می‌شود که امکان انتخاب کلاس،Data Context و MasterPage فراهم می‌باشد.

  9. از گزینه Data Context class گزینه New Data Context را انتخاب نمایید. صفحات مورد نیاز را در فولدر Views/Person ایجاد می‌نمایید.
  10. کد‌های تولید شده را می‌توانید بازبینی نمایید پروژه را اجرا تا خروجی کار را مشاهده نمایید.

مطالب
تبدیل بلوک‌های یونیکد در زیرنویس برای نمایش در تلویزیون‌ها و پلیرها
مقدمه
موقعی که سینمای ناطق کار خود را آغاز کرد، بسیاری از مردم از آن استقبال کردند و بسیاری از سینماگران که این استقبال را دیدند، رفته رفته به سمت سینمای ناطق کشیده شدند. ولی در این بین یک مشکلی ایجاد شده بود؛ اینکه ناشنوایان دیگر مانند قدیم یعنی دوران صامت نمی‌توانستند فیلم‌ها را تماشا کنند، پس نیاز بود این مشکل به نحوی رفع شود. از اینجا بود که ایده‌ی زیرنویس شکل گرفت و این مشکل را رفع نمود. بعدها فیلم‌ها انتقال دهنده‌ی فرهنگ و پیوند دهنده‌ی مردم با فرهنگ‌های مختلف شدند ولی تفاوت در زبان باعث می‌شد که این امر به خوبی صورت نگیرد. به همین علت زیرنویس، وظیفه‌ی دیگری را هم پیدا کرد و آن رساندن پیام فیلم با زبان خود مخاطب بود. امروزه تهیه‌ی زیرنویس‌ها توسط بسیاری از افراد که با زبان انگلیسی (آشنایی با یک زبان میانی برای ترجمه زیرنویس) آشنایی دارند رواج پیدا کرده و روزانه نزدیک به صد زیرنویس یا گاها بیشتر با زبان‌های مختلف بر روی اینترنت قرار می‌گیرند. بزرگترین سایتی که در حال حاضر با شهرت جهانی در این زمینه فعالیت دارد سایت  subscene.com  است.

آشنایی با انواع زیرنویس‌ها
زیرنویس‌ها فرمت‌های مختلفی دارند مانند srt,sub idx,smi و ... ولی در حال حاضر معروف‌ترین و معتبرترین فرمت در بین همه‌ی فرمت‌ها Subrip  با پسوند SRT می‌باشد که قالب متنی به صورت زیر دارد:
203
00:16:38,731 --> 00:16:41,325
<i>Happy Christmas, your arse
I pray God it's our last</i>
که باعث میشود حجم بسیار کمی در حد چند کیلوبایت داشته باشد.

بررسی مشکل ما با زیرنویس در تلویزیون‌ها
یکی از مشکلاتی که ما در اجرای زیرنویس‌ها بر روی تلویزیون‌ها داریم این است که حروف فارسی را به خوبی نمی‌شناسند و در هنگام نمایش با مشکل مواجه می‌شوند که البته در اکثر مواقع با تبدیل زیرنویس از ANSI به Unicode یا UTF-8 مشکل حل می‌شود. ولی در بعضی مواقع تلویزیون یا پلیرها از پشتیبانی زبان فارسی سرباز می‌زنند و زیرنویس را به شکل زیر نمایش می‌دهند.
سلام = م ا ل س
به این جهت ما از یک برنامه به اسم srttouni استفاده می‌کنیم که با استفاده یک روش جایگزینی و معکوس سازی، مشکل ما را حل می‌کند. ولی باز هم این برنامه مشکلاتی دارد و از آنجا که برنامه نویس این برنامه که واقعا کمال تشکر را از ایشان، دارم مشخص نیست، مجبور شدم به جای گزارش، خودم این مشکلات را حل کنم. 
مشکلات این برنامه :
  • عدم حذف تگ‌ها ، گاها برنامه نویس‌ها از تگ هایی چون Bold,italic,underline,color استفاده می‌کنند که معدود برنامه‌هایی آن را پشتیبانی کرده و تلویزیون و پلیرها هم که اصلا پشتیبانی نمی‌کنند و باعث میشود که متن روی تلویزیون مثل کد html ظاهر شود
  • بعضی جملات دوبار روی صفحه ظاهر می‌شوند.
  • تنها یک فایل را در هر زمان تبدیل می‌کند. مثلا اگر یک سریال چند قسمته داشته باشید، برای هر قسمت باید زیرنویس را انتخاب کرده و تبدیل کنید، در صورتی که میتوان دستور داد تمام زیرنویس‌های داخل دایرکتوری را تبدیل کرد یا چند زیرنویس را برای این منظور انتخاب کرد.

نحوه‌ی خواندن زیرنویس با کدنویسی
با تشکر از دوست عزیز ما در این صفحه می‌توان گفت یک کد تقریبا خوب و جامعی را برای خواندن این قالب داریم. بار دیگر نگاهی به قالب یک دیالوگ در زیرنویس می‌اندازیم و آن را بررسی می‌کنیم:
203
00:16:38,731 --> 00:16:41,325
<i>Happy Christmas, your arse
I pray God it's our last</i>
اولین خط شامل شماره‌ی خط است که از یک آغاز می‌گردد تا به تعداد دیالوگ‌ها، خط دوم، زمان آغاز و پایان دیالوگ مورد نظر است، موقعی که دیالوگ روی صفحه ظاهر میشود تا موقعی که دیالوگ از روی صفحه محو شود که به ترتیب بر اساس ساعت:دقیقه:ثانیه و میلی ثانیه می‌باشد. خطوط بعدی هم متن دیالوگ است است و بعد از پایان متن دیالوگ یک خط خالی زیر آن قرار می‌گیرد تا نشان دهد این دیالوگ به پایان رسیده است. اگر همین خط خالی حذف گردد برنامه‌هایی چون Media player classic خطهای زیری را جز متن دیالوگ قبلی به حساب می‌آورند و شماره خط و زمان بندی دیالوگ بعدی به عنوان متن روی صفحه ظاهر می‌گردند و بعضی player‌ها هم قاطی کرده و کلا زیرنویس را نمی‌خوانند یا اون خط رو نشون نمیدن مثل Kmplayer و هر کدام رفتار خاص خودشان را بروز می‌دهند.
کد زیر در کلاس SubRipServices وظیفه‌ی خواندن محتوای فایل srt را بر اساس عبارتی که دادیم دارد:
private readonly static Regex regex_srt = new Regex(@"(?<sequence>\d+)\r\n(?<start>\d{2}\:\d{2}\:\d{2},\d{3}) --\> " +
            @"(?<end>\d{2}\:\d{2}\:\d{2},\d{3})\r\n(?<text>[\s\S]*?)\r\n\r\n", RegexOptions.Compiled);

 public string ToUnicode(string lines)
        {

        string subtitle= regex_srt.Replace(lines,delegate(Match m)
             {
                 string text = m.Groups["text"].Value;
                 //1.remove tags
                 text = CleanScriptTags(text);

                 //2.replace letters
                 PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
                 string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
                 return
                     string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value,
                         m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ;
             }
            );

            return subtitle;
        }
در اولین خط ما یک Regular Expersion یا یک عبارت با قاعده تعریف کردیم که در اینجا میتوانید با خصوصیات آن آشنا شوید. ما برای این کلاس یک الگو ایجاد کردیم و بر حسب این الگو، متن یک زیرنویس را خواهد گشت و خطوطی را که با این تعریف جور در می‌آیند و معتبر هستند، برای ما باز می‌گرداند.
عبارتهایی که به صورت <name>? تعریف شده‌اند در واقع یک نامگذاری برای هر قسمت از الگوی ما هستند تا بعدا این امکان برای ما فراهم شود که خطوط برگشتی را تجزیه کنیم که مثلا فقط قسمت متن را دریافت کنیم، یا فقط قسمت زمان شروع یا پایان را دریافت کنیم و ...
متد tounicode یک آرگومان متنی دارد (lines) که شامل محتویات فایل  زیرنویس است. متد Replace در شی regex_srt با هر بار پیدا کردن یک متن بر اساس الگو در رشته lines دلیگیتی را فرا می‌خواند که در اولین پارامتر آن که از نوع matchEvaluator است، شامل اطلاعات متنی است که بر اساس الگو، یافت شده است. خروجی آن از نوع string می‌باشد که با متن پیدا شده بر اساس الگو جابجا خواهد کرد و در نهایت بعد از چندین بار اجرا شدن، کل متن‌های تعویض شده، به داخل متغیر subtitle ارسال خواهند شد.
کاری که ما در اینجا می‌کنیم این است که هر دیالوگ داخل زیرنویس را بر اساس الگو، یافته و متن آن را تغییر داده و متن جدید را جایگزین متن قبلی می‌کنیم. اگر زیرنویس ما 800 دیالوگ داشته باشد این دلیگیت 800 مرتبه اجرا خواهد شد.
از آنجا که ما تنها می‌خواهیم متن زیرنویس را تغییر دهیم، در اولین خط فرامین این دلیگیت تعریف شده، متن مورد نظر را بر اساس همان گروه‌هایی که تعریف کرده‌ایم دریافت می‌کنیم و در متغیر text قرار می‌دهیم:
m.Groups["text"].Value
در مرحله‌ی بعدی ما اولین مشکلمان (حذف تگ‌ها)  را با تابعی به اسم CleanScriptTags برطرف میکنیم که کد آن به شرح زیر است:
 private static readonly Regex regex_tags = new Regex("<.*?>", RegexOptions.Compiled);
 private  string CleanScriptTags(string html)
        {
            return regex_tags.Replace(html, string.Empty);
        }
کد بالا از یک regular Expression دیگر جهت پیدا کردن تگ‌ها استفاده می‌کند و به جای آن‌ها عبارت "" را جایگزین می‌کند. این کد قبلا در سایت جاری در این صفحه توضیح داده شده است. خروجی این تابع را مجددا در text قرار می‌دهیم و به مرحله‌ی دوم، یعنی تعویض کاراکترها می‌رویم:
 PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
                 string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
برای اینکه دقیقا متوجه شویم قرار است چکاری انجام شود بیاید دو گروه یا بلوک مختلف در یونیکد را بررسی کنیم. هر بلوک کد در یونیکد شامل محدوده‌ای از کد پوینت هاست که نامی منحصرفرد برای خود دارد و هیچ کدام از کدپوینت‌ها در هر بلوک یا گروه، اشتراکی با بقیه‌ی بلوک‌ها ندارد. سایت codetable از آن دست سایت‌هایی است که اطلاعات خوبی در مورد کدهای یونیکد دارد. در قسمت Unicode Groups دو گروه برای زبان عربی وجود دارند که در جدول این گروه، هر سطر آن یکی از کدها را به صورت دسیمال، هگزا دسیمال و نام و نماد آن، نمایش می‌دهد.
^  ,   ^   Arabic Presentation Forms-A 
^^  Arabic Presentation Forms-B 
بلوک اول طبق گفته‌ی ویکی پدیا دسته‌ی متنوعی از حروف مورد نیاز برای زبان فارسی، اردو، پاکستانی و تعدادی از زبان‌های آسیای مرکزی است.
بلوک دوم شامل نمادها و نشانه‌های زبان عربی است و در حال حاضر برای کد کردن استفاده نمی‌شوند و دلیل حضور آن برای سازگاری با سیستم‌های قدیمی است.
اگر خوب به مشکلی که در بالا برای زیرنویس‌ها اشاره کردیم دقت کنید، گفتیم حروف از هم جدا نشان داده می‌شوند و اگر به بلوک دوم در لینک‌های داده شده نگاه کنید می‌بینید که حروف متصل را داراست. یعنی برای حرف س 4 حرف یا کدپوینت داراست : سـ برای کلماتی مثل سبد، ـس برای کلماتی مثل شانس، ـسـ برای کلماتی مثل بسیار، ولی خود س برای کلمات غیر متصل مثل ناس، البته بعضی حروف یک یا دو حالت می‌طلبند مثل د، ر که فقط دو حالت ـد و د ، ـر و ر را دارند یا مثل آ که یک حالت دارد.
من قبلا یک کلاس به نام lettersTable ایجاد کرده بودم (و دیگر نوشتن آن را ادامه ندادم) که برای هر حرف، یک آیتم در شی‌ءایی از نوع dictionary ساخته بودم و هر کدپوینت بلوک اول را در آن کلید و کد متقابلش را در بلوک دوم، به صورت مقدار ذخیره کرده بودم (گفتیم که هر نماد در بلوک اول، برابر با 4 نماد در بلوک دوم است؛ ولی ما در دیکشنری تنها مقدار اول را ذخیره می‌کنیم. زیرا کد بقیه نمادها دقیقا پشت سر یکدیگر قرار گرفته‌اند که می‌توان با یک جمع ساده از عدد 0 تا 3، به مقدار هر کدام از نمادها رسید. البته ناگفته نماند بعضی نمادها 2 عدد بودند که این هم باید بررسی شود). برای همین هر کاراکتر را با کاراکتر قبل و بعد می‌گرفتم و بررسی می‌کردم و از یک جدول دیکشنری دیگر هم به اسم specialchars هم استفاده کردم تا آن کاراکترهایی که تنها دو نماد یا یک نماد را دارند، بررسی کنم و این کاراکترها همان کاراکترهایی بودند که اگر قبل یک حرف هم بیایند، حرف بعدی به آن‌ها نمی‌چسبد. برای درک بهتر، این عبارت مثال زیر را  برای حرف س در نظر بگیرید:
مستطیل = چون بین هر دو طرف س حر وجود دارد قطعا باید شکل س به صورت ـسـ انتخاب شود ، حالا مثال زیر را در نظر بگیرید:
دست = دـست که اشتباه است و باید باشد دست یعنی شکل سـ باید صدا زده شود، پس این مورد هم باید لحاظ شود.
نمونه‌ای از کد این کلاس:
Dictionary<int ,int>  letters=new Dictionary<int, int>();

   //0=0x0 ,1=1x0 ,2=0x1 ,3=1x1
        private void FillPrimaryTable()
        {
            //آ
            letters.Add(1570, 65153);
            //ا
            letters.Add(1575, 65166);
            //أ
            letters.Add(1571, 65155);
            //ب
            letters.Add(1576, 65167);
            //ت
            letters.Add(1578, 65173);
            //ث
            letters.Add(1579, 65177);
            //ج
            letters.Add(1580, 65181);
.....
}

Dictionary<int,byte> specialchars=new Dictionary<int, byte>();

  private void SetSpecialChars()
        {
            //آ
            specialchars.Add(1570, 0);
            //ا
            specialchars.Add(1575, 0);
            //د2
            specialchars.Add(1583, 1);
            //ذ2
            specialchars.Add(1584, 1);
            //ر2
            specialchars.Add(1585, 1);
            //ز2
            specialchars.Add(1586, 1);
            //ژ
            specialchars.Add(1688, 1);
            //و2
            specialchars.Add(1608, 1);
            //أ
            specialchars.Add(1571, 1);

        }
کلاس بالا تنها برای ذخیره‌ی کدپوینت‌ها بود، ولی یک کلاس دیگر هم به اسم lettersCrawler نوشته بودم که متد آن وظیفه‌ی تبدیل را به عهده داشت.

در آن متد هر بار یک حرف را انتخاب می‌کرد و حرف قبلی و بعدی آن را ارسال می‌کرد تا تابع CalculateIncrease آن را محاسبه کرده و کاراکتر نهایی را باز گرداند و به متغیر finalText اضافه می‌کرد. ولی در حین نوشتن، زمانی را به یاد آوردم که اندروید به تازگی آمده بود و هنوز در آن زمان از زبان فارسی پشتیبانی نمی‌کرد و حروف برنامه‌هایی که می‌نوشتیم به صورت جدا از هم بود و همین مشکل را داشت که ما این مشکل را با استفاده از یک کلاس جاوا که دوست عزیزی آن را در اینجا به اشتراک گذاشته بود، حل می‌کردیم. پس به این صورت بود که از ادامه‌ی نوشتن کلاس انصراف دادم و از یک کلاس دقیق‌تر و آماده استفاده کردم.
در واقع این کلاس همین کار بالا را با روشی بهتر انجام می‌دهد. همه‌ی نمادها به طور دقیق‌تری کنترل می‌شوند حتی تنوین‌ها و دیگر علائم، همه نمادها با کدهای متناظر در یک آرایه ذخیره شده‌اند که ما در بالا از نوع Dictionary استفاده کرده بودیم.
تنها کاری که نیاز بود، باید این کد به سی شارپ تبدیل میشد و از آنجایی که این دو زبان خیلی شبیه به هم هستند، حدود ده دقیقه‌ای برای ویرایش کد وقت برد که می‌توانید کلاس نهایی را از اینجا دریافت کنید.
پس خط زیر در متد ToUnicode کار تبدیل اصلی را صورت می‌دهد:
  PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
بنابراین مرحله‌ی دوم انجام شد. این تبدیل در بسیاری از سیستم‌ها همانند اندروید کافی است؛ ولی ما گفتیم که تلویزیون یا پلیر به غیر از جدا جدا نشان دادن حروف، آن‌ها را معکوس هم نشان می‌دهند. پس باید در مرحله‌ی بعد آن‌ها را معکوس کنیم که اینکار با خط زیر و صدا زدن تابع ReverseText انجام میگیرد
 //3.reverse tags
                 text = ReverseText(text);
از آنجا که یک دیالوگ ممکن است چند خطی باشد، این معکوس سازی برای ما دردسر می‌شد و ترتیب خطوط هم معکوس می‌شد. پس ما با استفاده از کد زیر هر یک خط را شکسته و هر کدام را جداگانه معکوس می‌کنیم و سپس به یکدیگر می‌چسبانیم:
string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
همه‌ی ما معکوس سازی یک رشته را بلدیم، یکی از روش‌ها این است که رشته را خانه به خانه از آخر به اول با یک for بخوانیم یا اینکه رشته را به آرایه‌ای از کارکاکترها، تبدیل کنیم و سپس با Array.Reverse آن را معکوس کرده و خانه به خانه به سمت جلو بخوانیم و خیلی از روش‌های دیگر. ولی این معکوس سازی‌ها برای ما یک عیب هم دارد و این هست که این معکوس سازی روی نمادهایی چون . یا ! و  غیره که در ابتدا و انتهای رشته آمده‌اند و حروف انگلیسی، نباید اتفاق بیفتند. پس می‌بینیم که تابع معکوس سازی هم باز باید ویژه‌تر باشد. ابتدا قسمت‌های ابتدا و انتها را جدا کرده و از آن حذف می‌کنیم. سپس رشته را معکوس می‌کنیم. ولی ممکن هست و احتمال دارد که بین حروف فارسی هم حروف انگلیسی یا اعداد به کار رود که آن‌ها هم معکوس می‌شوند. برای همین بعد از معکوس سازی یکبار هم باید آن‌ها را با یک عبارت با قاعده یافته و سپس هر کدام را جداگانه معکوس کرده و سپس مثل روش بالا Replace کنیم و رشته‌های جدا شده را به ابتدا و انتهای آن، سر جای قبلیشان می‌چسبانیم.
این دو تابع برای معکوس کردن عادی یک رشته به کار می‌روند:
    private string Reverse(string text)
        {
            return Reverse(text,0,text.Length);
        }

        private string Reverse(string text,int start,int end)
        {
            if (end < start)
                return text;
            string reverseText = "";

            for (int i = end-1; i >=start; i--)
            {
                reverseText += text[i];
            }
            return reverseText;
        }
ولی این تابع ReverseText جمعی از عملیات معکوس سازی ویژه‌ی ماست؛ مرحله اول، مرحله دریافت و ذخیره‌ی حروف خاص در ابتدای رشته به اسم پیشوند prefix است:
  private string ReverseText(string text)
        {
            char[] chararray = text.ToCharArray();
            string reverseText = "";
            bool prefixcomp = false;
            bool postfixcomp = false;
            string prefix = "";
            string postfix = "";

            #region get prefix symbols
            for (int i = 0; i < chararray.Length; i++)
            {
                if (!prefixcomp)
                {
                    char ch =(char) chararray.GetValue(i) ;
                    if (ch< 130)
                    {
                        prefix += chararray.GetValue(i);
                    }
                    else
                    {
                        prefixcomp = true;
                        break;
                    }
                }
            }
            #endregion
}
مرحله‌ی دوم هم دریافت و ذخیره‌ی حروف خاص در انتهای رشته به اسم پسوند postfix است که به این تابع اضافه می‌کنیم:
 #region get postfix symbols
            for (int i = chararray.Length - 1; i >-1 ; i--)
            {
                if (!postfixcomp && prefix.Length!=text.Length)
                {
                    char ch = (char)chararray.GetValue(i);
                    if (ch < 130)
                    {
                        postfix += chararray.GetValue(i);
                    }
                    else
                    {
                        postfixcomp = true;
                        break;
                    }
                }
            }
            #endregion
مرحله‌ی سوم عملیات معکوس سازی روی رشته است و سپس با استفاده از یک Regular Expression حروف انگلیسی و اعداد بین حروف فارسی را یافته و یک معکوس سازی هم روی آن‌ها انجام می‌دهیم تا به حالت اولشان برگردند. کل عملیات معکوس سازی در اینجا به پایان می‌رسد:
  #region reverse text

            reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length);

        
            reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m)
            {
                return Reverse(m.Value);
            });
            #endregion
تعریف عبارت با قاعده‌ی بالا به اسم unTargetedLetters:
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled);
آخر سر هم رشته را به‌علاوه پیشوند و پسوند جدا شده بر می‌گردانیم:
return prefix+ reverseText+postfix;
کد کامل تابع بدین شکل در می‌آید:
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled);
private string ReverseText(string text)
        {
            char[] chararray = text.ToCharArray();
            string reverseText = "";
            bool prefixcomp = false;
            bool postfixcomp = false;
            string prefix = "";
            string postfix = "";

            #region get prefix symbols
            for (int i = 0; i < chararray.Length; i++)
            {
                if (!prefixcomp)
                {
                    char ch =(char) chararray.GetValue(i) ;
                    if (ch< 130)
                    {
                        prefix += chararray.GetValue(i);
                    }
                    else
                    {
                        prefixcomp = true;
                        break;
                    }
                }
            }
            #endregion

            #region get postfix symbols
            for (int i = chararray.Length - 1; i >-1 ; i--)
            {
                if (!postfixcomp && prefix.Length!=text.Length)
                {
                    char ch = (char)chararray.GetValue(i);
                    if (ch < 130)
                    {
                        postfix += chararray.GetValue(i);
                    }
                    else
                    {
                        postfixcomp = true;
                        break;
                    }
                }
            }
            #endregion

            #region reverse text

            reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length);

        
            reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m)
            {
                return Reverse(m.Value);
            });
            #endregion

          

            return prefix+ reverseText+postfix;
        }
در نهایت، خط آخر دلیگت همه چیز را طبق فرمت یک دیالوگ srt چینش کرده و بر می‌گردانیم.
return
                     string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value,
                         m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ;
رشته subtitle را به صورت srt ذخیره کرده و انکودینگ را هم Unicode انتخاب کنید و تمام.

نمایی از برنامه‌ی نهایی


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


روی پلیر یا تلویزیون



  نکته‌ی نهایی: هنگام تست زیرنویس روی فیلم متوجه شدم پلیر خطوط بلند را که در صفحه‌ی نمایش جا نمی‌شود، می‌شکند و به دو خط تقسیم می‌کند. ولی نکته‌ی خنده دار اینجا بود که خط اول را پایین می‌اندازد و خط دوم را بالا. برای همین این تکه کد را نوشتم و به طور جداگانه در گیت هاب هم قرار داده‌ام.
 
این تکه کد را هم بعد از
//1.remove tags
                 text = CleanScriptTags(text);
 به برنامه اضافه می‌کنیم:
  text =StringUtils.ConvertToMultiLine(text);
از این پس خطوط به طولی بین 30 کاراکتر تا چهل کاراکتر  شکسته خواهند شد و مشکل خطوط بلند هم نخواهیم داشت.
کد متد ConvertToMultiline:
namespace Utils
{
    public static class StringUtils
    {
        public static string ConvertToMultiLine(String text, int min = 30, int max = 40)
        {
            if (text.Trim() == "")
                return text;

            string[] words = text.Split(new string[] { " " }, StringSplitOptions.None);

            string text1 = "";
            string text2 = "";
            foreach (string w in words)
            {
                if (text1.Length < min)
                {
                    if (text1.Length == 0)
                    {
                        text1 = w;
                        continue;
                    }

                    if (w.Length + text1.Length <= max)
                        text1 += " " + w;
                }
                else
                    text2 += w + " ";

            }
            text1 = text1.Trim();
            text2 = text2.Trim();
            if (text2.Length > 0)
            {
                text1 += Environment.NewLine + ConvertToMultiLine(text2, min, max);
            }
            return text1;
        }
      
    }
}
آرگومان‌های min و max که به طور پیش فرض 30 و 40 هستند، سعی می‌کنند که هر خط را در نهایت به طور حدودی بین 30 تا 40 کاراکتر نگه دارند.
نکته پایانی : خوشحال میشم دوستان در این پروژه مشارکت داشته باشند و اگر جایی نیاز به اصلاح، بهبود یا ایجاد امکانی جدید دارد  کمک حال باشند و سعی کنند تا آنجا که می‌شود برنامه را روی net frame work 2. نگه دارند و بالاتر نبرند. چون استفاده کننده‌های این برنامه کاربران عادی و گاها با دانش پایین هستند و خیلی از آن‌ها هنوز از ویندوز xp استفاده می‌کنند تا در اجرای برنامه خیلی دچار مشکل نشده و راحت برای بسیاری از آن‌ها اجرا شود.

برنامه مورد نظر را به طور کامل می‌توانید از اینجا  یا اینجا به صورت فایل نهایی و هم سورس دریافت کنید. 
بازخوردهای دوره
جلوگیری از deadlock در برنامه‌های async
نکته جالبی بود، توی یه برنامه ای از چندین تسک استفاده کرده بودم و مشکل اصلی اینجا بوده که الان متوجه شدم. فقط برام عجیبه بعضی از best practice‌ها رو مایکروسافت به صورت پیش فرض لحاظ نمیکنه نمونه اش همین ConfigureAwait(false) !
حالتی وجود داره که ادامه فعالیت متد async بایستی حتما در context فراخوانش ادامه پیدا کنه یعنی حالتی که ConfigureAwait بایستی حتما true باشه؟
نظرات اشتراک‌ها
آیا WPF مرده است؟!
بسته به اینکه شما مرده رو چی در نظر بگیرید مسئله فرق دارد. 
1- اینکه احتما نسخه بعدی وجود ندارد.(احتمالا)
2- جایگزینی بهتری برای آن وجود دارد(100% خیر){ولی احتمالا تا چند وقت دیگر می‌آید زیاد است. چون مایکروسافت قصد دارد تغییراتی را در برنامه‌های توسعه ای خود ایجاد کند که با یک کد برای همه پلت فرم‌ها کد نویسی شود. به نظر من برنامه ای شبیه ویندوز استو با امکانات کامل بزودی جایگزین خواهد شد که با توجه به تکنلوژی Xaml  برای wpf کار‌ها کار سویچ سخت نخواهد شد}
اشتراک‌ها
ارائه اولین نسخه ویژوال استودیو برای سیستم عامل مکینتاش

مایکروسافت جدیدا اقدام به ارائه نسخه مک از برنامه ویژوال استودیو کرده است. این شرکت پیشتر نسخه‌ای به اسم ویژوال استودیو کد برای مک عرضه کرده بود که فقط دارای محیط کد نویسی بود (کاربران اپل تا قبل از این نسخه مجبور به نصب مجازی سازی و نصب ویندوز و سپس ویژوال استودیو بودند).

ارائه اولین نسخه ویژوال استودیو برای سیستم عامل مکینتاش