مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنت‌ها
در قسمت قبل موفق شدیم بر اساس OpenAPI specification endpoint تنظیم شده، رابط کاربری خودکاری را توسط ابزار Swagger-UI تولید کنیم. در ادامه می‌خواهیم این مستندات تولید شده را غنی‌تر کرده و کیفیت آن‌را بهبود دهیم.


استفاده از XML Comments برای بهبود کیفیت مستندات API

نوشتن توضیحات XML ای برای متدها و پارامترها در پروژه‌های دات‌نتی، روشی استاندارد و شناخته شده‌است. برای نمونه در AuthorsController، می‌خواهیم توضیحاتی را به اکشن متد GetAuthor آن اضافه کنیم:
/// <summary>
/// Get an author by his/her id
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <returns>An ActionResult of type Author</returns>
[HttpGet("{authorId}")]
public async Task<ActionResult<Author>> GetAuthor(Guid authorId)
در این حالت اگر برنامه را اجرا کنیم، این توضیحات XMLای هیچ تاثیری را بر روی OpenAPI specification تولیدی و در نهایت Swagger-UI تولید شده‌ی بر اساس آن، نخواهد داشت. برای رفع این مشکل، باید به فایل OpenAPISwaggerDoc.Web.csproj مراجعه نمود و تولید فایل XML متناظر با این توضیحات را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
پس از تنظیم خاصیت GenerateDocumentationFile به true، با هر بار Build برنامه، فایل xml ای مطابق نام اسمبلی برنامه، در پوشه‌ی bin آن تشکیل خواهد شد؛ مانند فایل bin\Debug\netcoreapp2.2\OpenAPISwaggerDoc.Web.xml در این مثال.
اکنون نیاز است وجود این فایل را به تنظیمات SwaggerDoc در کلاس Startup برنامه، اعلام کنیم:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
                    // ... 
                   );

                var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);
                setupAction.IncludeXmlComments(xmlCommentsFullPath);
            });
        }
در متد IncludeXmlComments، بجای ذکر صریح نام و مسیر فایل OpenAPISwaggerDoc.Web.xml، بر اساس نام اسمبلی جاری، نام فایل XML مستندات تعیین و مقدار دهی شده‌است.
پس از این تنظیمات اگر برنامه را اجرا کنیم، در Swagger-UI حاصل، این تغییرات قابل مشاهده هستند:




افزودن توضیحات به Response

تا اینجا توضیحات پارامترها و متدها را افزودیم؛ اما response از نوع 200 آن هنوز فاقد توضیحات است:


علت را نیز در تصویر فوق مشاهده می‌کنید. قسمت responses در OpenAPI specification، اطلاعات خودش را از اسکیمای مدل‌های مرتبط دریافت می‌کند. بنابراین نیاز است کلاس DTO متناظر با Author را به نحو ذیل تکمیل کنیم:
using System;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author with Id, FirstName and LastName fields
    /// </summary>
    public class Author
    {
        /// <summary>
        /// The id of the author
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// The first name of the author
        /// </summary>
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        public string LastName { get; set; }
    }
}
مشکل! در این حالت اگر برنامه را اجرا کنیم، خروجی این توضیحات را در قسمت schemas مشاهده نخواهیم کرد. علت اینجا است که چون اسمبلی OpenAPISwaggerDoc.Models با اسمبلی OpenAPISwaggerDoc.Web یکی نیست و آن‌را از پروژه‌ی اصلی خارج کرده‌ایم، به همین جهت نیاز است ابتدا به فایل OpenAPISwaggerDoc.Models.csproj مراجعه و GenerateDocumentationFile آن‌را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
</Project>
سپس باید فایل xml مستندات آن‌را به صورت مجزایی به تنظیمات ابتدایی برنامه معرفی نمود:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
  // ...
                   );
                var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly).ToList();
                xmlFiles.ForEach(xmlFile => setupAction.IncludeXmlComments(xmlFile));
            });
        }
با توجه به اینکه تمام فایل‌های xml تولید شده در آخر به پوشه‌ی bin\Debug\netcoreapp2.2 کپی می‌شوند، فقط کافی است حلقه‌ای را تشکیل داده و تمام آن‌ها را یکی یکی توسط متد IncludeXmlComments به تنظیمات AddSwaggerGen اضافه کرد.

در این حالت اگر مجددا برنامه را اجرا کنیم، خروجی ذیل را در قسمت schemas مشاهده خواهیم کرد:



بهبود مستندات به کمک Data Annotations

اگر به اکشن متد UpdateAuthor در کنترلر نویسندگان دقت کنیم، چنین امضایی را دارد:
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
جائیکه موجودیت Author را در پروژه‌ی OpenAPISwaggerDoc.Entities تعریف کرده‌ایم، نام و نام خانوادگی اجباری بوده و دارای حداکثر طول 150 حرف، هستند. قصد داریم همین ویژگی‌ها را به DTO دریافتی این متد، یعنی AuthorForUpdate نیز اعمال کنیم:
using System.ComponentModel.DataAnnotations;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author for update with FirstName and LastName fields
    /// </summary>
    public class AuthorForUpdate
    {
        /// <summary>
        /// The first name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string LastName { get; set; }
    }
}
پس از افزودن ویژگی‌های Required و MaxLength به این DTO، خروجی Sawgger-UI به صورت زیر بهبود پیدا می‌کند:



بهبود مستندات متد HttpPatch با ارائه‌ی یک مثال

دو نگارش از اکشن متد UpdateAuthor در این مثال موجود هستند:
یکی HttpPut است
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
و دیگری HttpPatch:
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
            Guid authorId,
            JsonPatchDocument<AuthorForUpdate> patchDocument)
این مورد آرام آرام در حال تبدیل شدن به یک استاندارد است؛ چون امکان Partial updates را فراهم می‌کند. به همین جهت نسبت به HttpPut، کارآیی بهتری را ارائه می‌دهد. اما چون پارامتر دریافتی آن از نوع ویژه‌ی JsonPatchDocument است و مثال پیش‌فرض مستندات آن، آنچنان مفهوم نیست:


بهتر است در این حالت مثالی را به استفاده کنندگان از آن ارائه دهیم تا در حین کار با آن، به مشکل برنخورند:
/// <summary>
/// Partially update an author
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <param name="patchDocument">The set of operations to apply to the author</param>
/// <returns>An ActionResult of type Author</returns>
/// <remarks>
/// Sample request (this request updates the author's first name) \
/// PATCH /authors/id \
/// [ \
///     { \
///       "op": "replace", \
///       "path": "/firstname", \
///       "value": "new first name" \
///       } \
/// ] \
/// </remarks>
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
    Guid authorId,
    JsonPatchDocument<AuthorForUpdate> patchDocument)
در اینجا در حین کامنت نویسی، می‌توان از المان remarks، برای نوشتن توضیحات اضافی مانند ارائه‌ی یک مثال، استفاده کرد که در آن op و path معادل‌های بهتری را نسبت به مستندات پیش‌فرض آن پیدا کرده‌‌اند. در اینجا برای ذکر خطوط جدید باید از \ استفاده کرد؛ وگرنه خروجی نهایی، در یک سطر نمایش داده می‌شود:



روش کنترل warningهای کامنت‌های تکمیل نشده

با فعالسازی GenerateDocumentationFile در فایل csproj برنامه، کامپایلر، بلافاصله برای تمام متدها و خواص عمومی که دارای کامنت نیستند، یک warning را صادر می‌کند. یک روش برطرف کردن این مشکل، افزودن کامنت به تمام قسمت‌های برنامه است. روش دیگر آن، تکمیل خواص کامپایلر، جهت مواجه شدن با عدم وجود کامنت‌ها در فایل csproj برنامه است:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <WarningsAsErrors>NU1605;</WarningsAsErrors>
    <NoWarn>1701;1702;1591</NoWarn>
  </PropertyGroup>
توضیحات:
- اگر می‌خواهید خودتان را مجبور به کامنت نویسی کنید، می‌توانید نبود کامنت‌ها را تبدیل به error کنید. برای این منظور خاصیت TreatWarningsAsErrors را به true تنظیم کنید. در این حالت هر کامنت نوشته نشده، به صورت یک error توسط کامپایلر گوشزد شده و برنامه کامپایل نخواهد شد.
- اگر TreatWarningsAsErrors را خاموش کردید، هنوز هم می‌توانید یکسری از warningهای انتخابی را تبدیل به error کنید. برای مثال NU1605 ذکر شده‌ی در خاصیت WarningsAsErrors، مربوط به package downgrade detection warning است.
- اگر به warning نبود کامنت‌ها دقت کنیم به صورت عبارات warning CS1591: Missing XML comment for publicly visible type or member شروع می‌شود. یعنی  CS1591 مربوط به کامنت‌های نوشته نشده‌است. می‌توان برای صرفنظر کردن از آن، شماره‌ی این خطا را بدون CS، توسط خاصیت NoWarn ذکر کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-03.zip

در قسمت بعد، مشکل خروجی تولید response از نوع 200 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.
نظرات مطالب
آپلود فایل‌ها توسط برنامه‌های React به یک سرور ASP.NET Core به همراه نمایش درصد پیشرفت
با سلام؛ فرض کنید در یک فرم ثبت محصول، فیلد عنوان محصول و آپلود تصویر را داریم و یک دکمه آپلود فایل برای آپلود تصویر و یک دکمه برای ثبت محصول. یک حالت این است که کاربر تصویر را آپلود کند و بعد عنوان را پر کند و در نهایت دکمه ثبت را بزند، ولی فکر کنید کاربر تصویر را آپلود میکند و به هر دلیلی عنوان را پر نمیکند و دکمه ثبت را نمیزند؛ حال تکلیف تصویر آپلود شده چه میشود؟ آیا راهی وجود دارد برای حل این مشکل؟
بازخوردهای پروژه‌ها
راهنمایی در مورد تحلیل محصول و قیمت
سلام . تشکر بابت پروژه هایی که منتشر می‌کنید .
آقای ربال ، ببینید در مورد محصول و قیمت درست متوجه شدم ؟
یک رابطه یک به چند بین محصول و قیمت وجود دارد که یکی از مزیت‌های این میتونه نمودار بالا پایین تغییرات قیمت محصول باشه . آیا آخرین قیمت رو شما در جدول Product ذخیره میکنید ؟ به نظر شما بهتر است در جدول Product ذخیره شود و یا در جدول Price ؟
ممنون
راهنماهای پروژه‌ها
لیست مثال‌های همراه با سورس کد PdfReport
برای دریافت یکجای تمامی مثال‌های ذیل به این آدرس مراجعه کرده و بر روی لینک دانلود کلیک نمائید. همچنین توضیحات بیشتر و تکمیلی این مثال‌ها، در برچسب PdfReport سایت، قابل پیگیری و مطالعه هستند.

AccountingBalanceColumn/
چگونه باید از مقدار مانده ردیف قبلی در محاسبات ردیف جاری استفاده کرد (چیزی شبیه به گزارشات دفتر کل حسابداری).

AcroFormTemplate/
چگونه می‌توان از قالب‌های سفارشی تهیه شده توسط Open office در PdfReport استفاده کرد. اگر در یک سلول قرار است قالب پیچیده‌ای را نمایش دهید، یکی از روش‌های انجام کار استفاده از قالب‌های AcroForm است.

AdHocColumns/
چگونه تولید ستون‌های گزارشات را پویا کنیم (بدون نیاز به تعریف جزئیات آن‌ها). برای مثال اگر هربار کوئری متفاوتی را ارسال می‌کنید یا از منابع داده مختلفی با تعداد ستون‌های متغیر در گزارش نهایی استفاده می‌شود، می‌توانید با حذف قسمت تعاریف ستون‌ها، این نوع گزارشات پویا را تهیه نمائید.

AnnotationField/
نمایشی از قالب سلول سفارشی AnnotationField. Annotationها اشیایی خاص در فایل‌های PDF هستند که امکان نوشتن توضیحات طولانی را فراهم می‌کنند و نهایتا به شکل یک آیکون در گزارش ظاهر خواهند شد.

Barcodes/
مثالی در مورد نحوه تولید انواع بارکدهای مختلف مانند barcode 128 و barcode 39

CalculatedFields/
چگونه بر اساس فیلدهای موجود یک گزارش، ستون محاسبه شده جدیدی را تولید کنیم. همچنین مواردی مانند فرمت کردن عدد نمایش داده شده و اضافه کردن جمع به یک ستون نیز در این گزارش لحاظ شده است.

CharacterMap/
گزارشی شبیه به برنامه معروف character map ویندوز. در این گزارش نوع جدول به TableType.HorizontalStackPanel تنظیم شده است. به این ترتیب رکوردهای تولید شده به صورت افقی و پی در پی نمایش داده خواهند شد.

ChartImage/
نحوه قرار دادن نمودارهای MS Chart را در گزارشات، در این مثال مشاهده خواهید کرد.

CsvToPdf/
چگونه رکوردهای یک فایل CSV را تبدیل به فایل PDF کنیم؟ این مثال در حقیقت نحوه استفاده مستقیم از نتایج کوئری‌های LINQ را بیان می‌کند.

CustomCellTemplate/
چگونه یک قالب سلول سفارشی را تعریف کنیم. یک سری قالب پیش فرض مانند تصویر، متن و غیره در PdfReport به ازای هر سلول قابل تعریف است. اگر این موارد نیاز کاری شما را برآورده نمی‌کنند، می‌توانید آن‌ها را سفارشی سازی کنید.

CustomHeaderFooter/
چگونه هدر و فوتر گزارشات را سفارشی سازی کنیم؟

CustomPriceNumber/
چگونه یک قالب سلول سفارشی را جهت نمایش ویژه عدد مبلغ هر ردیف به شکل یک جدول پر شده از اعداد ایجاد کنیم.

DataAnnotations/
چگونه تعاریف خواص ستون‌ها را به کمک data annotations انجام داده و اینکار را ساده‌تر نمائیم. با استفاده از data annotations نیز می‌توان قسمت تعاریف ستون‌ها را کاملا حذف کرد.

DbImage/
چگونه تصاویر ذخیره شده در بانک اطلاعاتی را در گزارشات نمایش دهیم.

DigitalSignature/
چگونه امضای دیجیتال را به گزارشات PDF خود اضافه نمائیم.

DuplicateColumns/
چگونه از ستون‌هایی هم نام، استفاده کنیم. برای مثال اگر از دو جدول کوئری می‌گیرید و دو فیلد به نام‌های name اما با معانی و مقادیری متفاوت تعریف شده‌اند، چگونه باید ایندکس آن‌ها را جهت تمایز بهتر معرفی کرد.

DynamicCompile/
چگونه سورس یک گزارش PdfReport را به صورت پویا از یک فایل متنی ساده خوانده و کامپایل کنیم.

DynamicCrosstab/
چگونه یک گزارش Crosstab پویا را تعریف کنیم. برای مثال گزارشی که تعداد ستون‌های آن نامشخص است و هر بار بر اساس بازه روزهای گزارش‌گیری تعیین می‌شود.

EmailInMemoryPdf/
چگونه یک فایل Pdf تولید شده را به صورت خودکار به مقصدی خاص ایمیل کنیم.

Events/
چگونه می‌توان دقیقا پیش و پس از یک گزارش، تعاریف و عناصر دلخواه خود را اضافه کنیم؟

ExcelToPdf/
چگونه یک فایل اکسل را تبدیل به گزارش PdfReport کنیم؟

ExpensesCrosstab/
مثالی دیگر از نحوه تولید گزارشات Crosstab.

ExtraHeadingCells/
چگونه گزارشاتی را تولید کنیم که هدر آن‌ها بیش از یک ردیف است.

Grouping/
نحوه گروه بندی اطلاعات را در این گزارش بررسی خواهیم کرد.

HexDump/
یک گزارش ویژه از منبع داده‌ای anonymously typed.

HtmlCellTemplate/
چگونه می‌توان از Html جهت ساده سازی تعریف سلول‌های پیچیده که بیش از یک مقدار را نمایش می‌دهند استفاده کرد.

HtmlHeader/
چگونه می‌توان از Html برای ساده سازی هدر گزارش استفاده کرد.

HtmlHeaderRtl/
نسخه راست به چپ و فارسی مثال قبل.

IList/
چگونه می‌توان از لیست‌های جنریک گزارش تهیه کرد.

ImageFilePath/
چگونه می‌توان از تصاویر ذخیره شده در فایل سیستم، گزارش گرفت.

InjectCustomRows/
چگونه می‌توان ردیفی سفارشی را در بین ردیف‌های دریافت شده از بانک اطلاعاتی قرار داد.

InlineProviders/
چگونه می‌توان تعاریف سفارشی سلول‌ها را در همان محل تعریف گزارش به نحوی ساده‌تر تعریف کرد.

InMemory/
نحوه تولید فایل‌های PDF درون حافظه‌ای، مناسب جهت برنامه‌های وب ASP.NET (بدون نیاز به ذخیره فایل بر روی سرور)

MailingLabel/
چگونه گزارش‌های معروف برچسب‌های چاپی را توسط PdfReport تولید کنیم.

MasterDetails/
چگونه از روابط one-to-many بین دو جدول گزارش گیری کنیم؟

MergePdfFiles/
چگونه از چند منبع داده مختلف استفاده کرده و نهایتا گزارشات حاصل را یکی و تبدیل به یک فایل PDF کنیم. برای نمونه استفاده از سه جدول مختلف با هدرها و سرستون‌های متفاوت و سپس تولید یک گزارش یکپارچه از این سه، در قالب یک فایل نهایی. به علاوه در این مثال نحوه بازنویسی فوتر موجود نیز نمایش داده شده است (توسط WriterCustomizer آن).

MonthCalendar/
چگونه در گزارشات، تقویم میلادی را نمایش دهیم.

PdfA/
چگونه خروجی PDF حاصل را بر اساس استاندارد PdfA که مخصوص آرشیو و نگهداری است، تولید کنیم.

PersianFontsListToPdf/
چگونه از لیست قلم‌های نصب شده در سیستم گزارش Pdf تهیه کنیم.

PersianMonthCalendar/
بررسی نحوه نمایش تقویم شمسی، در گزارشات.

PersianRtl/
بررسی امکانات فارسی توکار کتابخانه PdfReport؛ مانند تهیه گزارشات راست به چپ، تاریخ شمسی، عدد به حروف و غیره.

ProgressReport/
چگونه درصد پیشرفت یک عملیات را در سلول‌ها نمایش دهیم. همچنین نحوه ایجاد گزارشات چند ستونی، برای صرفه جویی در میزان کاغذ مصرفی چاپ گزارشات را نیز در این گزارش مشاهده خواهید نمود.

QuestionsAcroForm/
مثالی در مورد نحوه استفاده از قالب‌های PDF تولید شده توسط Open office برای تولید برگه سؤالات امتحانی

QuestionsForm/
مثالی در مورد نحوه طراحی برگه سؤالات امتحانی توسط سفارشی سازی سلول‌ها در PdfReport

SQLiteDataReader/
چگونه از یک بانک اطلاعاتی SQLite گزارش تهیه کنیم.

StackedProperties/
چگونه در یک گزارش، در یک سلول بیش از یک فیلد را نمایش دهیم.

Tax/
چگونه یک گزارش فاکتور فروش طراحی کنیم.

WorkedHours/
چگونه گزارش حضور و غیاب پرسنل را تهیه کنیم.

WrapGroupsInColumns/
چگونه گزارشات چندستونی را تولید کنیم.

XmlToPdf/
چگونه داده‌های یک فایل XML را تبدیل به گزارش کنیم.

ZapfDingbatsSymbols/
چگونه از قلم مخصوص Symbols شرکت Adobe برای نمایش اشکال مختلف می‌توان استفاده کرد. 
مطالب
زیر نویس فارسی ویدیوهای ساخت برنامه‌های مترو توسط سی شارپ و XAML - قسمت پنجم

زیرنویس‌های فارسی قسمت پنجم «Building Windows 8 Metro Apps in C# and XAML» را از اینجا و یا اینجا می‌تونید دریافت کنید.

لیست سرفصل‌های قسمت پنجم به شرح زیر است:

Application Model  00:59:50 
Metro and WinRT introduce some significant changes to the world in which applications execute.
This module describes the implications for developers.
Introduction
Application Lifecycle
Demo: Application Lifecycle
Managing State
Demo: Saving State
Splash Screens
Launching Applications
Application Manifest
Packaging
Summary


این قسمت به جزئیات نحوه اجرای برنامه‌های مترو می‌پردازد. اگر با IIS کار کرده باشید، سیکل اجرایی برنامه‌های مترو ویندوز 8، همانند سیکل اجرایی برنامه‌های ASP.NET شده است! ویندوز مختار است برنامه شما را پس از مدتی بیکاری (البته این مدت در اینجا فقط 5 ثانیه است!)، معلق کرده یا حتی خاتمه دهد و تمام این‌ها هم از دید کاربر نهایی مخفی است. مانند زمانیکه یک برنامه ASP.NET پس از مدتی بیکاری، توسط IIS خاتمه می‌یابد (از حافظه خارج می‌شود) و پس از مدتی با رسیدن یک درخواست جدید، یک پروسه جدید برای اجرای آن ایجاد شده و مجددا سایت شروع به کار خواهد کرد؛ اینجا هم در دنیای مترو تقریبا به همین نحو با یک برنامه رفتار می‌شود.
یک نکته جالب دیگر هم در برنامه‌های مترو وجود دارد: ترد اصلی برنامه از ترد رابط کاربری جدا شده است. برای مثال سازنده کلاس App برنامه در یک ترد و رابط کاربری برنامه در ترد مجزای دیگری اجرا می‌شوند.
به علاوه روش‌های متفاوتی هم برای اجرای برنامه‌های مترو درنظر گرفته شده. دیگر فقط حالت کلیک بر روی یک برنامه سبب اجرای آن نمی‌شود. می‌توان بر اساس اتصال یک سخت افزار خاص به سیستم یا حتی یک جستجو هم سبب اجرای برنامه‌ای شد. برای مثال می‌توانید برنامه خود را طوری طراحی کنید که نتیجه‌ی جستجویی را در سیستم نمایش دهد.
سیستم بسته بندی برنامه‌های مترو نیز بسیار شبیه به فایل‌های XAP برنامه‌های سیلورلایت است که همه چیز داخل یک فایل قرار داده می‌شود؛ از فایل‌های تنظیمات برنامه تا فایل‌های کامپایل شده و منابع مورد نیاز. البته در اینجا نامش به appx تغییر یافته است به علاوه یک فایل cer که حاوی مجوز دیجیتال اجباری توزیع برنامه‌های مترو در فروشگاه ویندوز است.

مطالب
نوشتن یک بات تلگرامی با استفاده از webhookها
با رشد روز افزون شبکه‌های اجتماعی و نیاز روزمره مردم به این شبکه‌ها ،اکثر شبکه‌های اجتماعی با در اختیار قرار دادن کتاب خانه‌ها و apiها، توسعه و طراحی یک برنامه‌ی مبتنی بر آن‌ها را فراهم کرده‌اند. تلگرام نیز یکی  از این شبکه‌ها است و با طراحی بات‌ها میتوان یک نرم افزار کوچک و پر کاربرد را جهت آن طراحی کرد.
در این مقاله قصد دارم نحوه ساخت یک بات تلگرامی را با استفاده از webhook که پیشنهاد خود تلگرام میباشد و همچنین کار با سایر apiهای مانند گرفتن عکس پروفایل و ... به اشتراک بگذارم. ما آموزش را بنا بر یک مثال کاربردی، در قالب یک بات تلگرامی قرار می‌دهیم که بعد از start شدن، پیغام خوش آمد گویی را نمایش میدهد و سپس جملات فارسی را از کاربر دریافت و معادل انگلیسی آن‌ها را با استفاده از از google translate به کاربر نشان میدهد.


شروع به ساخت بات

 به طور کلی دو روش برای ساخت یک بات تلگرامی وجود دارد:
1- استفاده از کتابخانه‌های آماده
2 - استفاده webhook

در روش 1، از یک سری از کتابخانه‌های آماده و تعریف شده، استفاده میکنیم.

مزایا:
 بدون زحمت زیادی و فقط با فراخوانی توابع آماده، قادر خواهیم بود یک بات خیلی ساده را شبیه سازی کنیم.
هزینه آن نسبت به webhook کمتر است و شما میتوانید با یک vps، بات را اجرا کنید.

معایب:
1- این روش ازpolling استفاد میکند. یعنی دستور دریافت شده در یک حلقه‌ی بی نهایت قرا میگیرد و هر بار چک میشود که آیا درخواستی رسیده است یا خیر؟ که سربار بالایی را بر روی سرور ما خواهد داشت.
2- بعد از مدتی down میشود.
3- اگر شمار درخواست‌ها بالا رود، Down میشود.

روش  دیگر استفاده از webhook است که اصولی‌ترین روش و روشی است که خود سایت تلگرام آن را پیشنهاد داده‌است. اگر بخواهم توضیح کوتاهی درباره webhook بدهم، با استفاده از آن میتوانید تعیین کنید وقتی یک event، رخ‌داد، api ایی فرخوانی شود؛ یا مثلا شما یک سایت را با api نوشته‌اید (ASP.NET Web API) و آن را پابلیش کرده‌اید و الان میخواهید یک api جدید را بنویسید. در این حالت با استفاده از webhook، دیگر نیازی نیست تا کل پروژه را پابلیش کنید. یک پروژه api را می‌نویسید و آن را آپلود می‌کنید و درقسمت تنظیم وب هوک، آدرس دامین خودتون را می‌دهید. حتی میتوانید آن را با php  یا هر زبانی که میتوانید بنویسید.

 معایب:
1- هزینه آن. شما علاوه بر تهیه‌ی هاست و دامین، باید ssl را هم فعال کنید که در ادامه بیشتر توضیح خواهیم داد. البته نگرانی برای پیاده سازی ssl نیست. چون سایت‌هایی هستند که این سرویس‌ها را به صورت رایگان در اختیار شما میگذارند (مانند Lets encrypt).
2- تنظیم آن به مراتب سخت‌تر از روش قبل است.

مزایا:
1- سرعت آن بیشتر است.
2- درخواست‌های با تعداد بالا را می‌توان به راحتی پاسخ داد.
3- وابستگی ثالثی ندارد.


اولین مرحله ساخت بات

تا اینجای کار به مباحث تئوری بات‌ها پرداختیم. حال وارد اولین مرحله‌ی ساخت بات‌ها میشویم. قبل از شروع، شما باید در بات BotFather@ عضو شوید و سپس یک بات جدید را بسازید. برای آموزش ساخت بات در BotFather، میتوانید از این مبحث استفاده کنید. بعد از ساخت بات در BotFather، شما داری یک token خواهید شد که یک رشته‌ی کد شده‌است.


ایجاد پروژه‌ی جدید بات

- در ادامه سراغ ویژوال استودیو رفته و یک پروژه‌ی Web api Empty را ایجاد کنید.
- سپس وارد سایت تلگرام شوید و کتابخانه‌ی مربوطه را دریافت کنیدو یا میتوانید با استفاده از دستور زیر، این کتابخانه را نصب کنید:
 Install-Package Telegram.Bot
پس از آن، اولین کار، ایجاد یک controller جدید به نام Webhook میباشد. درون این کنترلر، یکaction متد جدید را به نام UpdateMsg ایجاد می‌کنیم:
[HttpPost]
public async Task<IHttpActionResult> UpdateMsg(Update update)
{
  //......
}
تمام درخواست‌های تلگرام (وقایع رسیده‌ی از آن) ،به این action متد ارسال خواهند شد. اگر دقت کنید این متد دارای یک آرگومان از نوع update میباشد که شامل تمام پراپرتی‌های یک درخواست، اعم از نام کاربری، نوع درخواست، پیام و ... است.


تنظیم کردن WebHook

- حال به قسمت تنظیم کردن webhook می‌رسیم. وارد فایل Global.asax.cs برنامه شوید و با دستور زیر، وب هوک را تنظیم کنید:
var bot = new Telegram.Bot.TelegramBotClient("Token");
bot.SetWebhookAsync("https://Domian/api/webhook").Wait();
- در قسمت token ،token خود را که از BotFather دریافت کردید، وارد کنید و در قسمت setwebhook، باید ادرس دامنه‌ی خود را وارد نمائید. البته برای آزمایش برنامه، ما دامنه‌ای نداریم و  قصد خرید هاستی را هم نداریم. بنابراین با استفاده از ابزار ngrok می‌توان به سادگی یک دامنه‌ی آزمایشی SSL را تهیه کرد و از آن استفاده نمود.
- در این حالت بعد از اجرای ngrok، آدرس https آن را کپی کرده و در قسمت بالا، بجای Domain ذکر شده قرار دهید.
- برای آزمایش انجام کار، یک break-point را در قسمت action متد یاد شده قرار دهید و سپس برنامه را اجرا کنید.
- اکنون از طریق تلگرام وارد بات شوید و یک درخواست را ارسال کنید.
- اگر کار را به درستی انجام داده باشید، در صفحه ngrok  پیغام 200 مبتنی بر ارسال صحیح درخواست را دریافت خواهید کرد و همچنین در قسمت breakpoints برنامه بر روی آرگومان update، میتوانید پراپرتی‌های یک درخواست را به صورت کامل دریافت کنید.
- ارسال اولین درخواست ما از طریق بات start/ میباشد. در این حالت میتوان دریافت که کاربر برای بار اول است که از بات استفاده میکند.
- در اکشن متد از طریق خاصیت update.Message.Text میتوان به متن فرستاده شده دسترسی داشت.
- همچین اطلاعات کاربر در update.Message.From، همراه با درخواست، فرستاده می‌شود.


کار با ابزار ترجمه‌ی گوگل و تکمیل پروژه‌ی Web API

اکنون طبق مثال بالا می‌خواهیم وقتی کاربر برای اولین بار وارد شد، پیغام خوش آمد گویی به او نمایش داده شود. بعد از آن هر متنی را که فرستاد، معنای آن را از گوگل ترنسلیت گرفته و مجددا به کاربر ارسال میکنیم. برای اینکار کلاس WebhookController را به شکل زیر تکمیل خواهیم کرد: 
namespace Telegrambot.Controllers
{
    public class WebhookController : ApiController
    {
        Telegram.Bot.TelegramBotClient _bot = new Telegram.Bot.TelegramBotClient("number");
        Translator _translator = new Translator();

        [HttpPost]
        public async Task<IHttpActionResult> UpdateMsg(Update update)
        {
            if (update.Message.Text == "/start")
            {
                await _bot.SendTextMessageAsync(update.Message.From.Id, "Welcome To My Bot");
            }
            else
            {
                var translatedRequest = _translator.Translate(update.Message.Text, "Persian", "English");
                await _bot.SendTextMessageAsync(update.Message.From.Id, translatedRequest);
            }
            return Ok(update);
        }
    }
}
توضیحات:
- با استفاده از update.Message.From.Id میتوان پیغام را به شخصی که درخواست داده‌است فرستاد.
- دقت کنید هنگام ارسال درخواست، در ngrok آیا درخواستی فرستاده می‌شود یا خطایی وجود دارد.

نکته! برای استفاده از بات باید حتما از ssl استفاده کنید. اگر نیاز به خرید این سرویس را ندارید، از این لینک نیز می‌توانید سرویس مورد نظر را بعد از 24 ساعت بر روی دامین خود تنظیم کنید.


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

شما میتوانید از این لینک پروژه بالا را دریافت و اجرا کنید .
نظرات اشتراک‌ها
50 سالگی زبان BASIC!
اولین زبانی بود که باهاش تونستم یک برنامه بنویسم. بهترین زبان آموزشی در دوران خودش برای آموزش بود.
همونطور که میدونید Basic یک زبان مفسری است و خطوط کد پس از Enter زدن از جهت نحو بررسی می‌شوند.
یکی از نکات مهم‌اش هم که برای من عادت شده بود نوشتن CLS، (یعنی همان Clear Screen) در خط ابتدای برنامه بود که هیچ وقت فراموش نمی‌شد.
به‌هر حال معتقدم کامپایلرهای قوی امروزی هر چیزی که دارند رو مدیون زبان‌هایی مثل همین بیسیک و پاسکال و C هستند.
مطالب
افزودن تصدیق ایمیل به ASP.NET Identity در MVC 5
در پست قبلی نحوه سفارشی کردن پروفایل کاربران در ASP.NET Identity را مرور کردیم. اگر بیاد داشته باشید یک فیلد آدرس ایمیل به کلاس کاربر اضافه کردیم. در این پست از این فیلد استفاده میکنیم تا در پروسه ثبت نام ایمیل‌ها را تصدیق کنیم. بدین منظور پس از ثبت نام کاربران یک ایمیل فعالسازی برای آنها ارسال می‌کنیم که حاوی یک لینک است. کاربران با کلیک کردن روی این لینک پروسه ثبت نام خود را تایید می‌کنند و می‌توانند به سایت وارد شوند. پیش از تایید پروسه ثبت نام، کاربران قادر به ورود نیستند.


در ابتدا باید اطلاعات کلاس کاربر را تغییر دهید تا دو فیلد جدید را در بر گیرد. یک فیلد شناسه تایید (confirmation token) را ذخیره می‌کند، و دیگری فیلدی منطقی است که مشخص می‌کند پروسه ثبت نام تایید شده است یا خیر. پس کلاس ApplicationUser  حالا باید بدین شکل باشد.
public class ApplicationUser : IdentityUser
{
    public string Email { get; set; }
    public string ConfirmationToken { get; set; }
    public bool IsConfirmed { get; set; }
}
اگر پیش از این کلاس ApplicationUser را تغییر داده اید، باید مهاجرت‌ها را فعال کنید و دیتابیس را بروز رسانی کنید. حالا می‌توانیم از این اطلاعات جدید در پروسه ثبت نام  استفاده کنیم و برای کاربران ایمیل‌های تاییدیه را بفرستیم.
private string CreateConfirmationToken()
{
    return ShortGuid.NewGuid();
}
 
private void SendEmailConfirmation(string to, string username, string confirmationToken)
{
    dynamic email = new Email("RegEmail");
    email.To = to;
    email.UserName = username;
    email.ConfirmationToken = confirmationToken;
    email.Send();
}
 
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        string confirmationToken = CreateConfirmationToken();
        var user = new ApplicationUser()
        {
            UserName = model.UserName,
            Email = model.Email,
            ConfirmationToken = confirmationToken, 
                IsConfirmed = false };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            SendEmailConfirmation(model.Email, model.UserName, confirmationToken);
            return RedirectToAction("RegisterStepTwo", "Account");
        }
        else
        {
            AddErrors(result);
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}
برای تولید شناسه‌های تایید (tokens) از کلاسی بنام ShortGuid استفاده شده است. این کلاس یک مقدار GUID را encode می‌کند که در نتیجه آن مقدار خروجی کوتاه‌تر بوده و برای استفاده در URL‌ها ایمن است. کد این کلاس را از این وبلاگ گرفته ام. پس از ایجاد حساب کاربری باید شناسه تولید شده را به آن اضافه کنیم و مقدار فیلد IsConfirmed را به false تنظیم کنیم. برای تولید ایمیل‌ها من از Postal استفاده می‌کنم. Postal برای ساختن ایمیل‌های دینامیک شما از موتور Razor استفاده می‌کند. می‌توانید ایمیل‌های ساده (plain text) یا HTML بسازید، عکس و فایل در آن درج و ضمیمه کنید و امکانات بسیار خوب دیگر. اکشن متد RegisterStepTwo تنها کاربر را به یک View هدایت می‌کند که پیامی به او نشان داده می‌شود.
بعد از اینکه کاربر ایمیل را دریافت کرد و روی لینک تایید کلیک کرد به اکشن متد RegisterConfirmation باز می‌گردیم.
private bool ConfirmAccount(string confirmationToken)
{
    ApplicationDbContext context = new ApplicationDbContext();
    ApplicationUser user =  context.Users.SingleOrDefault(u => u.ConfirmationToken == confirmationToken);
    if (user != null)
    {
        user.IsConfirmed = true;
        DbSet<ApplicationUser> dbSet = context.Set<ApplicationUser>();
        dbSet.Attach(user);
        context.Entry(user).State = EntityState.Modified;
        context.SaveChanges();
 
        return true;
    }
    return false;
}
 
[AllowAnonymous]
public ActionResult RegisterConfirmation(string Id)
{
    if (ConfirmAccount(Id))
    {
        return RedirectToAction("ConfirmationSuccess");
    }
    return RedirectToAction("ConfirmationFailure");
}
متد ConfirmAccount سعی می‌کند کاربری را در دیتابیس پیدا کند که شناسه تاییدش با مقدار دریافت شده از URL برابر است. اگر این کاربر پیدا شود، مقدار خاصیت IsConfirmed را به true تغییر می‌دهیم و همین مقدار را به تابع باز می‌گردانیم. در غیر اینصورت false بر می‌گردانیم. اگر کاربر تایید شده است، می‌تواند به سایت وارد شود. برای اینکه مطمئن شویم کاربران پیش از تایید ایمیل شان نمی‌توانند وارد سایت شوند، باید اکشن متد Login را کمی تغییر دهیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null && user.IsConfirmed)
        {
            await SignInAsync(user, model.RememberMe);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}
تنها کاری که می‌کنیم این است که به دنبال کاربری می‌گردیم که فیلد IsConfirmed آن true باشد. اگر مقدار این فیلد false باشد کاربر را به سایت وارد نمی‌کنیم و پیغام خطایی نمایش می‌دهیم.
همین. این تمام چیزی بود که برای اضافه کردن تصدیق ایمیل به اپلیکیشن خود نیاز دارید. از آنجا که سیستم ASP.NET Identity با Entity Framework مدیریت می‌شود و با مدل Code First ساخته شده، سفارشی کردن اطلاعات کاربران و سیستم عضویت ساده‌تر از همیشه است.



توضیحاتی درباره کار با Postal

اگر به متد SendEmailConfirmation دقت کنید خواهید دید که آبجکتی از نوع Email می‌سازیم (که در اسمبلی‌های Postal وجود دارد) و از آن برای ارسال ایمیل استفاده می‌کنیم. عبارت "RegEmail" نام نمایی است که باید برای ساخت ایمیل استفاده شود. این متغیر از نوع dynamic است، مانند خاصیت ViewBag. بدین معنا که می‌توانید مقادیر مورد نظر خود را بصورت خواص دینامیک روی این آبجکت تعریف کنید. از آنجا که Postal از موتور Razor استفاده می‌کند، بعدا در View ایمیل خود می‌توانید به این مقادیر دسترسی داشته باشید.
در پوشه Views پوشه جدیدی بنام Emails بسازید. سپس یک فایل جدید با نام RegEmail.cshtml در آن ایجاد کنید. کد این فایل را با لیست زیر جایگزین کنید.
To: @ViewBag.To
From: YOURNAME@gmail.com
Subject: Confirm your registration

Hello @ViewBag.UserName,
Please confirm your registration by following the link bellow.

@Html.ActionLink(Url.Action("RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }), "RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }, null)
این فایل، قالب ایمیل‌های شما خواهد بود. ایمیل‌ها در حال حاظر بصورت plain text ارسال می‌شوند. برای اطلاعات بیشتر درباره ایمیل‌های HTML و امکانات پیشرفته‌تر به سایت پروژه Postal  مراجعه کنید.

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

  • ViewBag.To آدرس ایمیل گیرنده را نشان می‌دهد.
  • ViewBag.UserName نام کاربر جاری را نمایش می‌دهد.
  • ViewBag.ConfirmationToken شناسه تولید شده برای تایید کاربر است.
در این قالب لینکی به متد RegisterConfirmation در کنترلر Account وجود دارد که شناسه تایید را نیز با پارامتری بنام id انتقال می‌دهد.

یک فایل ViewStart.cshtml_ هم در این پوشه بسازید و کد آن را با لیست زیر جایگزین کنید.
@{ Layout = null; /* Overrides the Layout set for regular page views. */ }
نظرات مطالب
خواندنی‌های 9 اردیبهشت
تولید کننده‌های unit test روش غلطی از دیدگاه تئوری محسوب می‌شوند چون کلا test driven development به معنای اول نوشتن تست و بعد برنامه نویسی است.
تولید خودکار متدهای تست از روی کدهای موجود می‌تونه بر اساس یک متد باگ دار هم تولید بشه که نهایتا نتیجه جالبی را برای شما تولید نخواهد کرد.
و کلا این دیدگاه که کدوم بهتره، این یا اون و غیره رو کنار بگذارید. یک فریم ورک تست را پیدا کنید و با آن ادامه بدهید. هدف نوشتن تست است. این فریم ورک‌ها حداکثر چند قابلیت بیشتر یا کمتر دارند.
مطالب
خلاصه‌ای در مورد وضعیت فعلی MySQL

MySQL مدتی است که جزو یکی از محصولات شرکت اوراکل محسوب شده و توسعه دهندگان تجاری باید برای استفاده از آن هزینه کنند. این هزینه نیز اخیرا افزایش یافته و به حداقل 2000 دلار به ازای هر سرور رسیده است (+). این عدد واقعا رقم بالایی برای محصولی محسوب می‌شود که بسیاری از توسعه دهنده‌ها تصور می‌کنند رایگان است. استفاده از این محصول با توجه به مدل تجاری جدید آن فقط در پروژه‌های سورس باز رایگان است (بله فقط در پروژه‌هایی که با مجوز GPL منتشر شوند) و اگر شما یک سیستم تجاری کلاینت سرور را بر این اساس طراحی کنید حتما باید هزینه‌های مرتبط را نیز پرداخت نمائید (+).
توضیحی در مورد GPL و MySQL
MySQL AB offers a commercial license for organizations that do not want to release the source code for their application.
The change from the LGPL to the GPL for the client libraries was made in 2001 during the development of MySQL 4.0 to help MySQL AB more easily differentiate between a proprietary user who should buy a commercial license and a free software user who should use the GPL license.

MySQL با توجه به مجوز GPL آن در شرایط زیر رایگان خواهد بود:
- قصد توزیع مجدد آن‌را نداشته باشید.
- همچنین برنامه‌ی شما نیز به صورت سورس باز تحت مجوز GPL ارائه گردد.
و تنها زمانی در مورد MySQL باید هزینه کنید که:
-قصد توزیع مجدد آن‌را داشته باشید.
-برنامه‌ی شما سورس باز نبوده و قصد ندارید آن‌را تحت مجوز GPL ارائه دهید. (که عموما در مورد برنامه‌های تجاری به همین صورت است)

نکته‌ی دیگری را که باید به آن دقت داشت این است که برای واگذاری MySQL به شرکت اوراکل،‌ اتحادیه اروپا نیز با توجه به وجود بیش از 50 هزار توسعه دهنده‌ی اروپایی که از MySQL استفاده می‌کنند، شرکت اوراکل را موظف کرده است تا این dual licensing (تجاری و سورس باز) را تا سال 2015 حفظ کرده و ادامه دهد (+). به این معنا که شرکت اوراکل پس از سال 2015 هیچگونه تعهدی به ارائه‌ی نگارش سورس باز این محصول به هیچ نهاد و یا سازمانی ندارد.
البته این‌ها به معنای پایان دنیا نیست. هم اکنون چهار fork سورس باز از این محصول وجود دارند (Drizzle ، MariaDB ، OurDelta و Percona Server) ولی تنها آینده است که میزان موفقیت، پایداری و تداوم آن‌ها را مشخص خواهد کرد.