اشتراک‌ها
Type Script و Angular 2

Angular 2 is written in and all the samples and documentation are written in TypeScript.  Thus, TypeScript is my next language of choice.  This should be pretty straight forward, as the hard part is OOP, which we C# programmers are already comfortable with. 

Type Script  و Angular 2
بازخوردهای پروژه‌ها
استفاده از DataAnnotations , dynamic column
سلام 
یه سوال از حضورتون این کدی که در حال حاضر استفاده میکنم ، . میخوام از (DisplayName) استفاده کنم که باز هم از خود نام پروپرتی‌ها در codefirst  استفاده میکنه ، میشه بفرمایید کدوم قسمت رو باید تغییر بدم
public IPdfReportData CreatePdfReport(string title)
        {
            if (title=="" || title==null)
            {
                title = "گذارش";
            }
            return new PdfReport().DocumentPreferences(doc =>
               {
                   doc.RunDirection(PdfRunDirection.RightToLeft);
                   doc.Orientation(PageOrientation.Portrait);
                   doc.PageSize(PdfPageSize.A4);
                   doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
               })
               .DefaultFonts(fonts =>
               {
                   fonts.Path(AppPath.ApplicationPath + "\\fonts\\BNazanin.ttf",
                                     Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
               })
                .PagesFooter(footer =>
                {
                    footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy"));
                })
                .PagesHeader(header =>
                {
                    header.DefaultHeader(defaultHeader =>
                    {
                       // defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                        defaultHeader.Message(title);
                    });
                })
                .MainTableTemplate(template =>
                {
                    template.BasicTemplate(BasicTemplate.SilverTemplate);
                })
                .MainTablePreferences(table =>
                {
                    table.ColumnsWidthsType(TableColumnWidthType.Relative);
                })
                .MainTableDataSource(dataSource =>
                {
                    Cash.Models.ApplicationDbContext db = new Models.ApplicationDbContext();
                    var listOfRows = db.Users.Join(

                        db.CheckOuts, u => u.Id, c => c.ApplicationUserID,
                        (u, c) => new { u.FName, u.Lname, u.NationalCode, c.Creditor, c.Debtor, c.Decsription }
                        ).Where(x => x.Creditor != 0).ToList();


                    dataSource.AnonymousTypeList(listOfRows);
                })
                .MainTableSummarySettings(summary =>
                {
                    summary.OverallSummarySettings("جمع کل");
                    summary.PreviousPageSummarySettings("نقل از صفحه قبل");
                    summary.PageSummarySettings("جمع صفحه");
                })
                .MainTableAdHocColumnsConventions(adHocColumns =>
                {
                    //We want sum of the int columns
                    adHocColumns.AddTypeAggregateFunction(
                        typeof(Int64),
                        new AggregateProvider(AggregateFunction.Sum)
                        {
                            DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)
                        });
                    adHocColumns.AddTypeAggregateFunction(
                      typeof(Decimal),
                      new AggregateProvider(AggregateFunction.Sum)
                      {
                          DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)
                      });

                    //We want to dispaly all of the dateTimes as ShamsiDateTime
                    adHocColumns.AddTypeDisplayFormatFormula(
                        typeof(DateTime),
                        data => { return PersianDate.ToPersianDateTime((DateTime)data); }
                    );
                    adHocColumns.ShowRowNumberColumn(true);
                    adHocColumns.RowNumberColumnCaption("ردیف");
                })
                .MainTableEvents(events =>
                {
                    events.DataSourceIsEmpty(message: "هیچ اطلاعاتی موجود نیست");
                })
                .Export(export =>
                {
                    export.ToExcel();
                    export.ToXml();
                })
                .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\AdHocColumnsSampleRpt.pdf"));
        }

مسیرراه‌ها
React 16x
پیش نیاز ها
کامپوننت ها
ترکیب کامپوننت ها
طراحی یک گرید
مسیریابی 
کار با فرم ها
ارتباط با سرور
احراز هویت و اعتبارسنجی کاربران 
React Hooks  
توزیع برنامه

مدیریت پیشرفته‌ی حالت در React با Redux و Mobx   

       Redux
       MobX  

مطالب تکمیلی 
    مطالب
    پیاده سازی پروژه‌های React با TypeScript - قسمت پنجم - تعیین نوع هوک useReducer
    هوک استاندارد useReducer، یک نمونه‌ی پیچیده‌تر هوک useState، برای مدیریت حالت است؛ با ساختاری شبیه به Redux، اما تنها مخصوص یک کامپوننت. هوک useReducer شبیه به یک نسخه‌ی کوچک و محلی Redux است. در این قسمت، نحوه‌ی تعیین نوع‌های قسمت‌های مختلف آن‌را با TypeScript بررسی می‌کنیم.


    ایجاد کامپوننت جدید ReducerButtons

    برای توضیح مفاهیم این قسمت، فایل جدید src\components\ReducerButtons.tsx را به همراه محتوای زیر ایجاد می‌کنیم:
    import React, { useReducer } from "react";
    
    const initialState = { rValue: true };
    type State = {  rValue: boolean; };
    type Action = {   type: string; };
    
    function reducer(state: State, action: Action) {
      switch (action.type) {
        case "one":
          return { rValue: true };
        case "two":
          return { rValue: false };
        default:
          return state;
      }
    }
    
    export const ReducerButtons = () => {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
        <div>
          {state?.rValue && <h1>Visible</h1>}
          <button onClick={() => dispatch({ type: "one" })}>Action One</button>
          <button onClick={() => dispatch({ type: "two" })}>Action Two</button>
        </div>
      );
    };
    توضیحات:
    - این کامپوننت دو دکمه را رندر می‌کند تا توسط آن‌ها بتوان چندین Action مختلف را جهت مدیریت حالت، سبب شد؛ مانند Action One و Two.
    - initialState را در این مثال، یک شیء ساده در نظر گرفته‌ایم که تنها دارای یک مقدار boolean است.
    - سپس نیاز به یک تابع ویژه را به نام reducer، داریم که مقدار state جاری و یک action را دریافت می‌کند. این تابع بر اساس نوع Action ای که به آن می‌رسد، state جدیدی را بازگشت می‌دهد و در قسمت رندر آن، با بررسی state?.rValue، سبب نمایش عبارتی خواهیم شد.
    - در حین تعریف هوک useReducer، قسمت dispatch آن، تابعی است که یک Action را اجرا می‌کند. فراخوانی آن‌را در رویداد onClick دو دکمه‌ی تعریف شده مشاهده می‌کنید. این تابع یک شیء را دریافت می‌کند که type اکشن ارسالی به تابع reducer را مقدار دهی می‌کند.

    توسط useReducer برای ایجاد تغییرات در شیء state کامپوننت جاری، از مفهومی به نام dispatch actions استفاده می‌شود. action در اینجا به معنای رخ‌دادن چیزی است؛ مانند کلیک بر روی یک دکمه و یا دریافت اطلاعاتی از یک API. در این حالت مقایسه‌ای بین وضعیت قبلی state و وضعیت فعلی آن صورت می‌گیرد و تغییرات مورد نیاز جهت اعمال به UI، محاسبه خواهند شد. اصلی‌ترین جزء آن، تابعی است به نام Reducer. این تابع، یک تابع خالص است و دو آرگومان را دریافت می‌کند. تابع Reducer، بر اساس action و یا رخ‌دادی، ابتدا کل state برنامه را دریافت می‌کند و سپس خروجی آن بر اساس منطق این تابع، یک state جدید خواهد بود. اکنون که این state جدید را داریم، برنامه‌ی React ما می‌تواند به تغییرات آن گوش فرا داده و بر اساس آن، UI را به روز رسانی کند.

    اولین قسمتی را که در حین کار با useReducer توسط TypeScript به آن برخورد خواهیم کرد، نمایش خطاهایی در مورد نوع‌های پارامترهای state و action تابع reducer است. در حالت متداول جاوا اسکریپتی آن، این پارامترها، بدون نوع ذکر می‌شوند که در اینجا به any تفسیر خواهند شد و یک چنین تعاریفی با توجه به strict بودن تنظیمات tsconfig.json برنامه، مجاز نیست. به همین جهت نیاز به تعریف دو type جدید به نام‌های State و Action در اینجا وجود دارد تا خطاهای بدون نوع بودن پارامترهای تابع reducer برطرف شوند. نوع Action به همراه حداقل یک خاصیت به نام action رشته‌ای است و نوع State بر اساس initialState ای که تعریف کردیم، دارای یک خاصیت متناظر boolean است.

    نکته‌ی جالب دومی که در اینجا توسط TypeScript گوشزد می‌شود، الزام به ذکر حالت default مربوط به switch ای است که در تابع reducer تعریف کرده‌ایم. اگر این حالت را حذف کنیم، بلافاصله خطای زیر را در قسمت تعریف useReducer نمایش می‌دهد:


    عنوان می‌کند که خروجی تابع reducer، یک state جدید است؛ اما ممکن است از نوع never هم باشد که قابلیت ترجمه‌ی به نوع state را ندارد.


    بهبود تعاریف نوع‌های useReducer

    تا اینجا مهم‌ترین تغییرات تایپ‌اسکریپتی صورت گرفته، تعریف نوع‌های پارامترهای تابع reducer است. اکنون فرض کنید می‌خواهیم، سومین Action را هم به کامپوننت جاری اضافه کنیم:
    <button onClick={() => dispatch({ type: "three" })}>Action Three</button>
    با این تغییر، برنامه بدون مشکل کامپایل می‌شود، اما ... در عمل کار خاصی را هم انجام نمی‌دهد؛ چون switch تعریف شده‌ی در تابع reducer، یک case مخصوص به آن‌را تعریف نکرده‌است. یا حتی ممکن است در حین تعریف همان دو تابع dispatch، مقدار type را به اشتباه وارد کنیم و با حالت‌های تعریف شده‌ی در تابع reducer تطابقی نداشته باشد. به همین جهت علاقمندیم تا TypeScript بتواند جلوی یک چنین اشتباهاتی را نیز بگیرد؛ برای این منظور می‌توان از union types استفاده کرد:
    type Action = {
       type: "one" | "two" | "three";
    };
    در اینجا تمام اکشن‌های ممکن و مدنظر را لیست کرده‌ایم که سبب خواهد شد تا اگر مقدار نوع اکشنی به درستی وارد نشود، بلافاصله خطایی را دریافت کنیم:


    و یا حتی اگر مقدار action.type ای را در تابع reducer به اشتباه وارد کردیم، باز هم برنامه کامپایل نمی‌شود:



    تا اینجا موفق شدیم جلوی ورود actionهای پیش بینی نشده را بگیریم. اما اگر در switch تعریف شده، "case "three را هم قید نکنیم، باز هم برنامه کامپایل می‌شود و خطایی گزارش نخواهد شد. برای این منظور فقط کافی است case default را حذف کنیم. چون action.type دیگر نمی‌تواند مقدار دیگری را بجز موارد ذکر شده‌ی در نوع Action داشته باشد. با حذف case default، اینبار ذکر "case "three یا هر کدام از مقادیر سه‌گانه‌ی مجازی که در اینجا تعریف کردیم، الزامی خواهد بود. در غیراینصورت، همان خطای دریافت خروجی never را از تابع reducer دریافت می‌کنیم که با ذکر هر سه case مجاز، برطرف می‌شود.
    با این تغییرات، کدهای نهایی این قسمت به صورت زیر در خواهند آمد:
    import React, { useReducer } from "react";
    
    const initialState = { rValue: true };
    
    type State = {
      rValue: boolean;
    };
    
    type Action = {
      type: "one" | "two" | "three";
    };
    
    function reducer(state: State, action: Action) {
      switch (action.type) {
        case "one":
          return { rValue: true };
        case "two":
          return { rValue: false };
        case "three":
          return { rValue: false };
      }
    }
    
    export const ReducerButtons = () => {
      const [state, dispatch] = useReducer(reducer, initialState);
    
      return (
        <div>
          {state?.rValue && <h1>Visible</h1>}
          <button onClick={() => dispatch({ type: "one" })}>Action One</button>
          <button onClick={() => dispatch({ type: "two" })}>Action Two</button>
          <button onClick={() => dispatch({ type: "three" })}>Action Three</button>
        </div>
      );
    };
    مطالب
    جایگزین کردن jQuery با JavaScript خالص - قسمت ششم - رخدادهای مرورگرها
    واکنش نشان دادن به تغییرات صفحات وب، قسمت مهم و عمده‌ای از کار توسعه‌ی برنامه‌های وب را تشکیل می‌دهد. مفاهیم مرتبط با DOM events از زمان IE 4.0 و Netscape Navigator version 2 به مرورگرها اضافه شدند و به مرور تکامل یافتند. پیش از ظهور مرورگرهای مدرن (به IE 9.0 و مرورگرهای پیش از آن، مرورگرهای «باستانی» گفته می‌شود) به علت عدم هماهنگی آن‌ها با استانداردهای وب و تفاوت روش‌های رخدادگردانی، jQuery نقش مهمی را در زمینه‌ی یکدست سازی کدهای مدیریت رخدادها در بین مرورگرهای مختلف ارائه کرد. اما با پیش‌رفت‌های صورت گرفته و هماهنگی بیشتر مرورگرها با استانداردهای وب، دیگر نیازی به jQuery برای ارائه‌ی کدهای یکدست رخدادگردانی نیست و کار مستقیم با API وب مرورگرها برای این منظور کافی است.


    انواع رخدادها: بومی و سفارشی

    دو رده بندی عمومی رخدادها در مرورگرها وجود دارند: بومی و سفارشی.
    بومی‌ها همان‌هایی هستند که در مستندات رسمی استانداردهای وب ذکر شده‌اند؛ مانند click که توسط ماوس و یا صفحه کلید فعال می‌شود و یا load که در زمان بارگذاری کامل صفحه، تصاویر و یا یک iframe رخ می‌دهد.
    رخدادهای سفارشی مواردی هستند که توسط یک کتابخانه‌ی خاص و یا جهت یک برنامه‌ی خاص تهیه شده‌اند. مانند یک رخداد سفارشی که زمان شروع آپلود یک فایل را اعلام می‌کند.
    رخدادهای سفارشی که بدون jQuery ایجاد و رخ‌می‌دهند، توسط jQuery نیز قابل بررسی و مدیریت هستند و نه برعکس. به عبارتی رخدادهای سفارشی ایجاد شده‌ی توسط jQuery غیراستاندارد بوده و صرفا مختص به API آن هستند.
    در این بین، شیء استاندارد Event کار اتصال رخدادهای سفارشی و استاندارد را انجام می‌دهد. هر نوع رخداد DOM (سفارشی و یا بومی)، توسط یک شیء Event بیان می‌شود که آن نیز به همراه تعدادی خاصیت و متد، جهت مدیریت این رخداد است. برای مثال رخداد click دارای خاصیت type ایی به نام click است که در شیء Event متناظر با آن تعریف شده‌است.


    انتشار رخدادها در صفحه

    در روزهای آغازین وب، Netscape روش event capturing را برای انتشار رخدادها در صفحه ارائه داد و در مقابل آن IE روش event bubbling را معرفی کرد که متضاد یکدیگر بودند. در سال 2000 با ارائه استاندارد DOM Level 2 Events Specification، این وضعیت تغییر کرد و شامل هر دو مورد event capturing و event bubbling است و در حال حاضر تمام مرورگرهای مدرن این استاندارد را پیاده سازی کرده‌اند. بر اساس این استاندارد، زمانیکه رویدادی خلق می‌شود، فاز capturing آغاز می‌گردد که از شیء window شروع، سپس به شیء document منتشر می‌شود و این روند تا رسیدن به المانی که سبب بروز رخداد شده‌است ادامه پیدا می‌کند. پس از پایان فاز capturing، فاز جدید bubbling شروع می‌شود. در این فاز، رخداد از تمام والدین شیء هدف عبور می‌کند تا به شیء window برسد.
    برای مثال اگر سند HTML ما چنین تعریفی را داشته باشد و بر روی المان «child of child of one» کلیک شده باشد:
       <!DOCTYPE html> 
       <html>
       <head>
         <title>event propagation demo</title>
       </head>
       <body>
         <section>
           <h1>nested divs</h1>
           <div>one
            <div>child of one
              <div>child of child of one</div>
            </div>
          </div>
        </section>
      </body>
      </html>
    این رخداد در فاز capturing از این المان‌ها عبور می‌کند:
    1.window
    2.document
    3.<html>
    4.<body>
    5.<section>
    6.<div>one
    7.<div>child of one
    8.<div>child of child of one
    و در فاز Bubbling از این المان‌ها:
    9.<div>child of child of one
    10.<div>child of one
    11.<div>one
    12.<section>
    13.<body>
    14.<html>
    15.document
    16.window
    هرچند به دلایل تاریخی و همچنین عدم پشتیبانی jQuery از فاز capturing، بیشتر از فاز Bubbling به صورت پیش‌فرض در رخدادگردانی استفاده می‌شود. همچنین صدور رخداد از المانی که آن‌را ایجاد کرده‌است، بیشتر منطقی به نظر می‌رسد تا عکس آن.
    البته باید درنظر داشت که jQuery از روش ارائه شده‌ی توسط مرورگر برای فاز Bubbling استفاده نمی‌کند و این مسیر را خودش مجددا محاسبه و رخدادگردان‌های این مسیر را به صورت دستی اجرا می‌کند. به همین جهت کارآیی آن نسبت به روش توکار و بومی مرورگرها کمتر است.


    ایجاد رخدادهای DOM و صدور آن‌ها در jQuery

    برای نمایش ایجاد و صدور رخدادهای DOM با و بدون jQuery، از قطعه کد HTML زیر استفاده می‌کنیم:
       <div>
         <button type="button">do something</button>
       </div>
     
      <form method="POST" action="/user">
         <label>Enter user name:
           <input name="user">
         </label>
         <button type="submit">submit</button>
      </form>
    jQuery به همراه دو متد trigger و triggerHandler برای ایجاد و انتشار رخدادها در طول DOM است. متد trigger سبب ایجاد، صدور و انتشار یک رخداد به تمام والدهای شیء صادر کننده‌ی رخداد می‌شود. نوع این انتشار نیز bubbling است. متد triggerHandler، فقط بر روی المانی که فراخوانی می‌شود، عمل کرده و سبب انتشار این رخداد از طریق bubbling نمی‌شود:
       // submits the form 
       $('FORM').trigger('submit');
     
       // submits the form by clicking the button 
       $('BUTTON[type="submit"]').trigger('click');
     
       // focuses the text input 
       $('INPUT').trigger('focus');
     
      // removes focus from the text input 
      $('INPUT').trigger('blur');
    در این مثال‌ها توسط متد trigger، به دو روش سبب submit یک فرم و همچنین در ابتدا سبب focus یک تکست باکس و سپس رفع آن شده‌ایم.
    هرچند روش دومی نیز در jQuery API برای انجام همینکارها نیز پیش بینی شده‌است:
       // submits the form 
       $('FORM').submit();
     
       // submits the form by clicking the button 
       $('BUTTON[type="submit"]').click();
     
       // focuses the text input 
       $('INPUT').focus();
     
      // removes focus from the text input 
      $('INPUT').blur();
    در اینجا به ازای هر رخداد، یک نام مستعار در jQuery API تدارک دیده شده‌است.

    در ادامه فرض کنید یک دکمه داخل یک div قرار گرفته‌است و آن div نیز به همراه یک مدیریت کننده‌ی رخداد کلیک است. در این حالت اگر بخواهیم با کلیک بر روی دکمه سبب اجرای رویدادگردان div والد نشویم، می‌توان از متد triggerHandler استفاده کرد:
      // clicks the first button - the click event does not bubble 
      $('BUTTON[type="button"]').triggerHandler('click');


    ایجاد رخدادهای DOM و صدور آن‌ها در جاوا اسکریپت (بدون استفاده از jQuery)

    در web API مرورگرها، برای انجام بروز رخدادهای معادل مثالی که با jQuery مطرح شد، می‌توان متدهای بومی متناظر با این رخدادها را بر روی المان‌ها فراخوانی کرد:
       // submits the form 
       document.querySelector('FORM').submit();
     
       // submits the form by clicking the button 
       document.querySelector('BUTTON[type="submit"]').click();
     
       // focuses the text input 
       document.querySelector('INPUT').focus();
     
      // removes focus from the text input 
      document.querySelector('INPUT').blur();
    قطعه کد فوق به علت استفاده‌ی از querySelector، با IE 8.0 و تمام مرورگرهای پس از آن سازگار است.
    متدهای توکار و بومی click ،focus و blur بر روی تمام عناصر DOM که از اینترفیس HTMLElement مشتق شده باشند، وجود دارند. متد submit فقط بر روی المان‌هایی از نوع <form> وجود دارد و قابل فراخوانی است.
    باید دقت داشت که فراخوانی متدهای click و submit از نوع bubbling است؛ اما متدهای focus و blur خیر. از این جهت که این دو رخداد فاز capturing را سبب می‌شوند.

    متدهای یاد شده را توسط سازنده‌ی شیء Event و یا متد createEvent شیء document نیز می‌توان ایجاد کرد. یکی از کاربردهای آن، ارائه‌ی رفتاری سفارشی مانند triggerHandler جی‌کوئری است:
       var clickEvent;
    
       if (typeof Event === 'function') {
         clickEvent = new Event('click', {bubbles: false});
       }
       else {
           clickEvent = document.createEvent('Event');
          clickEvent.initEvent('click', false, true);
      }
    
      document.querySelector('BUTTON[type="button"]').dispatchEvent(clickEvent);
    کار با سازنده‌ی شیء Event در تمام مرورگرهای جدید، منهای IE (تمام نگارش‌های آن) پشتیبانی می‌شود. در اینجا اگر این پشتیبانی وجود داشت، از خاصیت bubbles: false شیء Event استفاده می‌شود و اگر مرورگری قدیمی بود، از متد document.createEvent برای این منظور کمک گرفته می‌شود. در این حالت دومین پارامتر متد initEvent، همان bubbles است.


    ایجاد و صدور رخدادهای سفارشی

    فرض کنید در حال تهیه‌ی کتابخانه‌ای هستیم که افزودن و حذف آیتم‌ها را به یک گالری عکس ارائه می‌دهد. می‌خواهیم روشی را در اختیار مصرف کننده قرار دهیم تا بتواند به این رخدادهای سفارشی (غیر استانداردی که جزو W3C نیستند) گوش فرا دهد.
    در جی‌کوئری برای ایجاد رخدادهای سفارشی به صورت زیر عمل می‌شود:
      // Triggers a custom "image-removed" element, 
      // which bubbles up to ancestor elements.
    $libraryElement.trigger('image-removed', {id: 1});
    در اینجا نیز صدور رخدادها همانند قبل و توسط همان متد trigger است. اما مشکلی که با آن وجود دارد این است که گوش فرا دهنده‌ی به این رخداد نیز باید توسط جی‌کوئری ارائه شود و خارج از این کتابخانه قابل دریافت و پیگیری نیست.
    در خارج از جی‌کوئری و توسط web API استاندارد مرورگرها ایجاد و صدور رخدادهای سفارشی به همراه bubbling آن به صورت زیر است:
      var event = new CustomEvent('image-removed', {
        bubbles: true,
        detail: {id: 1}
      });
      libraryElement.dispatchEvent(event);
    البته باید به‌خاطر داشته باشید این روش صرفا با مرورگرهای جدید (منهای تمام نگارش‌های IE) کار می‌کند. در اینجا اگر نیاز به ارائه‌ی راه حلی سازگار با IE نیز وجود داشت می‌توان از document.createEvent استفاده کرد:
      var event = document.createEvent('CustomEvent');
      event.initCustomEvent('image-removed', false, true, {id: 1});
      libraryElement.dispatchEvent(event);
    و اگر بخواهیم بررسی وجود IE و یا پشتیبانی از CustomEvent را نیز قید کنیم، به قطعه کد زیر خواهیم رسید که با تمام مرورگرهای موجود کار می‌کند:
      var event;
     
       // If the `CustomEvent` constructor function is not supported, 
       // fall back to `createEvent` method. 
       if (typeof CustomEvent === 'function') {
         event = new CustomEvent('image-removed', {
           bubbles: true,
           detail: {id: 1}
         });
      }
      else {
          event = document.createEvent('CustomEvent');
          event.initCustomEvent('image-removed', false, true, {
            id: 1
          });
      }
    
      libraryElement.dispatchEvent(event);


    گوش فرادادن به رخدادهای صادر شده، توسط jQuery

    در جی‌کوئری با استفاده از متد on آن می‌توان به تمام رخدادهای استاندارد و همچنین سفارشی گوش فرا داد:
      $(window).on('resize', function() {
         // react to new window size 
      });
    در ادامه برای حذف تمام گوش فرا دهنده‌های به رخداد resize می‌توان از متد off آن استفاده کرد:
      // remove all resize listeners - usually a bad idea 
      $(window).off('resize');
    اما مشکلی را که این روش به همراه دارد، از کار انداختن تمام قسمت‌های دیگری است که هم اکنون به صدور این رخداد گوش فرا می‌دهند.
    روش بهتر انجام اینکار، ذخیره‌ی ارجاعی به متدی است که قرار است این رویداد گردانی را انجام دهد:
      var resizeHandler = function() {
          // react to new window size 
      };
    
      $(window).on('resize', resizeHandler);
    
      // ...later 
      // remove only our resize handler 
      $(window).off('resize', resizeHandler);
    و در آخر تنها این گوش فرا دهنده‌ی خاص را در صورت نیاز غیرفعال می‌کنیم و نه تمام گوش فرادهنده‌های سراسر برنامه را.

    همچنین اگر یک گوش فراهنده‌ی به رخدادی تنها قرار است یکبار در طول عمر برنامه اجرا شود، می‌توان از متد one استفاده کرد:
    $(someElement).one('click', function() {
        // handle click event 
    });
    پس از یکبار اجرای رخدادگردان کلیک در اینجا، از کلیک‌های بعدی صرفنظر خواهد شد.


    گوش فرادادن به رخدادهای صادر شده، توسط جاوا اسکریپت خالص (یا همان web API مرورگرها)

    ابتدایی‌ترین روش گوش فرادادن به رخدادها که از زمان آغاز معرفی آن‌ها در دسترس بوده‌است، روش تعریف inline آن‌ها است:
      <button onclick="handleButtonClick()">click me</button>
    در اینجا متد رویدادگردان به یکی از ویژگی المان انتساب داده می‌شود. مشکل این روش، نیاز به سراسری تعریف کردن متد handleButtonClick است و دیگر نمی‌توان آن‌را در فضای نامی خاص و یا سفارشی قرار داد.
    روش دیگر ثبت رویدادگردان click، انتساب متد آن به خاصیت رخداد متناظری در آن المان ویژه است:
      buttonEl.onclick = function() {
        // handle button click 
      };
    مزیت این روش، عدم نیاز به استفاده‌ی از متدهای سراسری است.
    البته باید دقت داشت که یکی از دو روش یاد شده را می‌توانید استفاده کنید. در اینجا آخرین رویدادگردان متصل شده‌ی به المان، همواره تمام نمونه‌های موجود دیگر را بازنویسی می‌کند.
    اگر نیاز به معرفی رویدادگردان‌های متعددی برای یک المان در ماژول‌های مختلف برنامه وجود داشت، از زمان IE 9.0 به بعد، متد addEventListener برای این منظور تدارک دیده شده‌است و syntax آن بسیار شبیه به متد on جی‌کوئری است:
      buttonEl.addEventListener('click', function() {
        // handle button click 
      });
    در این حالت دیگر مشکل نیاز به متدهای سراسری و یا عدم امکان تعریف بیش از یک رویدادگردان خاص برای المانی مشخص، دیگر وجود ندارد.
    برای نمونه معادل قطعه کد جی‌کوئری که پیشتر با متد on نوشتیم، با جاوا اسکریپت خالص به صورت زیر است:
      window.addEventListener('resize', function() {
        // react to new window size 
      });
    در اینجا برای حذف یک رویدادگردان می‌توان از متد removeEventListener استفاده کرد. تفاوت مهم آن با متد off جی‌کوئری این است که در اینجا حتما باید مشخص باشد کدام رویدادگردان را می‌خواهید حذف کنید:
      var resizeHandler = function() {
          // react to new window size 
      };
    
      window.addEventListener('resize', resizeHandler);
    
      // ...later 
      // remove only our resize handler 
      window.removeEventListener('resize', resizeHandler);
    یعنی روش حذف رویدادگردان‌ها در اینجا شبیه به مثال دومی است که برای متد off جی‌کوئری ارائه کردیم. ابتدا باید ارجاعی را به متد رویدادگردان تهیه کنیم و سپس بر اساس این ارجاع، امکان حذف آن وجود خواهد داشت.
    در اینجا حتی امکان تعریف متد one جی‌کوئری نیز پیش بینی شده‌است (البته جزو استانداردهای جدید وب از سال 2016 است):
      someElement.addEventListener('click', function(event) {
         // handle click event 
      }, { once: true });
    اگر بخواهیم متد one سازگار با مرورگرهای قدیمی‌تر را نیز ارائه کنیم، چنین شکلی را پیدا می‌کند:
      var clickHandler = function() {
        // handle click event 
        // ...then unregister handler 
        someElement.removeEventListener('click', clickHandler);
      };
      someElement.addEventListener('click', clickHandler);
    در اینجا پس از بروز رخداد، کار removeEventListener آن به صورت خودکار صورت می‌گیرد.


    کنترل انتشار رخدادها

    فرض کنید می‌خواهیم جلوی انتخاب المان‌های صفحه مانند تصاویر و متن را توسط ماوس بگیریم. روش انجام اینکار با jQuery به صورت زیر است:
    $(window).on('mousedown', function(event) {
        event.preventDefault();
    });
    و یا توسط web API مرورگرها به این صورت:
      window.addEventListener('mousedown', function(event) {
        event.preventDefault();
      });
    مطابق «W3C DOM Level 3 Events specification» عملکرد پیش‌فرض رخداد mousedown با انتخاب متون و یا کشیدن و رها کردن المان‌ها آغاز می‌شود. متد preventDefault یکی از متدهای شیء event است که به رویدادگردان‌های تعریف شده ارسال می‌شود و توسط آن عملکرد پیش‌فرض آن رخداد لغو می‌شود.

    برای جلوگیری کردن از انتشار رخدادی مانند click جهت رسیدن به سایر رویدادگردان‌های ثبت شده‌ی در بین راه فاز bubbling، می‌توان از متد stopPropagation استفاده کرد. روش انجام اینکار در جی‌کوئری:
      $someElement.on('click', function(event) {
          event.stopPropagation();
      });
    البته jQuery صرفا فاز انتشار از نوع bubbling را پشتیبانی می‌کند.
    و با web Api جهت جلوگیری از انتشار رخدادها در فاز capturing (این تنها راه مدیریت فاز capturing است):
      // stop propagation during capturing phase 
      someElement.addEventListener('click', function(event) {
          event.stopPropagation();
      }, true);
    و یا استفاده از web API برای جلوگیری از انتشار رخدادها در فاز bubbling:
      // stop propagation during bubbling phase 
      someElement.addEventListener('click', function(event) {
          event.stopPropagation();
      });
    البته باید درنظر داشت که متد stopPropagation از انتشار رخدادها به سایر گوش فرا دهنده‌های همان المان صادر کننده‌ی رخداد جلوگیری نمی‌کند. برای این منظور باید از متد stopImmediatePropagation استفاده کرد؛ در جی‌کوئری:
      $someElement.on('click', function(event) {
          event.stopImmediatePropagation();
      });
    و توسط web API مرورگرها:
      someElement.addEventListener('click', function(event) {
          event.stopImmediatePropagation();
      });

    یک نکته: در این حالت اگر متد رویدادگردانی مقدار false را برگرداند، به معنای فراخوانی هر دوی متد preventDefault و stopPropagation است.


    ارسال اطلاعات به رویدادگردان‌ها

    روش ارسال اطلاعات اضافی به رویداد گردان‌ها در جی‌کوئری به صورت زیر است:
      $uploaderElement.trigger('uploadError', {
        filename: 'picture.jpeg'
      });
    و رویدادگردان گوش فرا دهنده‌ی به آن، به این نحو می‌تواند به filename دسترسی پیدا کند:
      $uploaderParent.on('uploadError', function(event, data) {
         showAlert('Failed to upload ' + data.filename);
      });
    در اینجا دومین پارامتر تعریف شده، امکان دسترسی به تمام خواص سفارشی ارسالی را میسر می‌کند.

    روش انجام اینکار با web API مرورگرها به صورت زیر است:
       // send the failed filename w/ an error event
      var event = new CustomEvent('uploadError', {
         bubbles: true,
         detail: {filename: 'picture.jpeg'}
       });
       uploaderElement.dispatchEvent(event);
     
       // ...and this is a listener for the event 
       uploaderParent.addEventListener('uploadError', function(event) {
          showAlert('Failed to upload ' + event.detail.filename);
      });
    این روش با تمام مرورگرهای مدرن (منهای تمام نگارش‌های IE) کار می‌کند و روش دسترسی به خاصیت detail سفارشی تعریف و ارسال شده، از طریق همان خاصیت event ارسالی به رویدادگردان است.
    و اگر می‌خواهید از IE هم پشتیبانی کنید، روش جایگزین کردن شیء CustomEvent با createEvent به صورت زیر است:
      // send the failed filename w/ an error event 
       var event = document.createEvent('CustomEvent');
       event.initCustomEvent('uploadError', true, true, {
         filename: 'picture.jpeg'
       });
       uploaderElement.dispatchEvent(event);
     
       // ...and this is a listener for the event 
       uploaderParent.addEventListener('uploadError', function(event) {
        showAlert('Failed to upload ' + event.detail.filename);
      });


    متوجه شدن زمان بارگذاری یک شیء در صفحه

    در حین توسعه‌ی برنامه‌های وب، با این نوع سؤالات زیاد مواجه خواهید شد: چه زمانی تمام و یا بعضی از المان‌های صفحه کاملا بارگذاری و رندر شده‌اند؟
    پاسخ به این نوع سؤالات در W3C UI Events specification توسط رویداد استاندارد load داده شده‌است.

    - چه زمانی تمام المان‌های موجود در صفحه کاملا بارگذاری و رندر شده و همچنین شیوه‌نامه‌های تعریف شده نیز به آن‌ها اعمال گردیده‌اند؟
    روش انجام اینکار با jQuery:
      $(window).on('load', function() {
        // page is fully rendered 
      });
    و توسط web API بومی مرورگرها که بسیار مشابه نمونه‌ی jQuery است:
      window.addEventListener('load', function() {
        // page is fully rendered 
      });

    - چه زمانی markup استاتیک صفحه‌ی جاری در جای خود قرار گرفته‌اند؟
    اهمیت این موضوع، به دسترسی به زمان مناسب و امن ایجاد تغییرات در DOM بر می‌گردد. برای این منظور رویداد استاندارد DOMContentLoaded پیش‌بینی شده‌است که زودتر از رویداد load، در دسترس برنامه نویس قرار می‌گیرد. در جی‌کوئری توسط یکی از دو روش معروف زیر به رویداد یاد شده دسترسی خواهید داشت:
      $(document).ready(function() {
        // markup is on the page 
      });
    
      //or
      $(function() {
        // markup is on the page 
      });
    و معادل web API آن در تمام مرورگرهای جدید، همان تعریف رویدادگردانی برای DOMContentLoaded استاندارد است:
      document.addEventListener('DOMContentLoaded', function() {
        // markup is on the page 
      });

    یک نکته: بهتر است این تعریف web API را پیش از تگ‌های <link> قرار دهید. زیرا بارگذاری آن‌ها، اجرای هر نوع اسکریپتی را تا زمان پایان عملیات، سد می‌کند.

    - چه زمانی المانی خاص در صفحه بارگذاری شده‌است و چه زمانی بارگذاری یک المان با شکست مواجه شده‌است؟
    در جی‌کوئری توسط بررسی رویدادهای load و error می‌توان به وضعیت نهایی بارگذاری المان‌هایی خاص دسترسی یافت:
       $('IMG').on('load', function() {
         // image has successfully loaded 
       });
     
       $('IMG').on('error', function() {
         // image has failed to load 
       });
    روش انجام اینکار با web API مرورگرها نیز یکی است:
      document.querySelector('IMG').addEventListener('load', function() {
        // image has successfully loaded 
      });
    
      document.querySelector('IMG').addEventListener('error', function() {
        // image has failed to load 
      });

    - جلوگیری از ترک اتفاقی صفحه‌ی جاری
    گاهی از اوقات نیاز است برای از جلوگیری از تخریب صفحه‌ی جاری و از دست رفتن اطلاعات ذخیره نشده‌ی کاربر، اگر بر روی دکمه‌ی close بالای صفحه کلیک کرد و یا کاربر به اشتباه به صفحه‌ی دیگری هدایت شد، جلوی اینکار را بگیریم. برای این منظور رخداد استاندارد beforeunload درنظر گرفته شده‌است. روش استفاده‌ی از این رویداد در جی‌کوئری:
      $(window).on('beforeunload', function() {
        return 'Are you sure you want to unload the page?';
      });
    و در web API مرورگرها:
      window.addEventListener('beforeunload', function(event) {
        var message = 'Are you sure you want to unload the page?';
        event.returnValue = message;
        return message;
      });
    در حالت web API بعضی از مرورگرها از نتیجه‌ی متد استفاده می‌کنند و بعضی دیگر از مقدار خاصیت event.returnValue. به همین جهت هر دو مورد در اینجا مقدار دهی شده‌اند.
    اشتراک‌ها
    استفاده از GoogleMaps در Android (پایه)
    Without a doubt, maps are one of the most useful tools for users when included in an app. This tutorial is the first in a series going over Google Maps v2 for Android. It will cover setting up the Google Maps API through the Google Developer Console, including a map fragment in your applications, displaying the user's location, adding markers, drawing on the map, and some general methods that will add utility to your app.
    Intermediate  & Advanced
    استفاده از GoogleMaps در Android (پایه)
    مطالب
    مستند سازی 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 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.
    اشتراک‌ها
    Paper Cut یک ایمیل سرور دسکتاپ
    If you ever send emails from an application or web site during development, you're familiar with the fear of an email being released into the wild. Are you positive none of the 'test' emails are addressed to colleagues or worse, customers? Of course, you can set up and maintain a test email server for development -- but that's a chore. Plus, the delay when you are waiting to view new test emails can radically slow your development cycle 
    Paper Cut یک ایمیل سرور دسکتاپ
    اشتراک‌ها
    قدرت برنامه نویسی تنبل طور

    Here are 13 techniques and tools that prove the power of lazy programming. The next time the boss tells you it’s time to roll up your sleeves and lean into the console, head to the nap room instead. 

    قدرت برنامه نویسی تنبل طور
    مطالب
    استفاده از Google Analytics API در دات نت فریم ورک

    بالاخره گوگل کار تهیه API مخصوص ابزار Analytics خود را به پایان رساند و اکنون برنامه نویس‌ها می‌توانند همانند سایر سرویس‌های گوگل از این ابزار گزارشگیری نمایند.
    خلاصه کاربردی این API ، دو صفحه تعاریف پروتکل (+) و ریز مواردی (+) است که می‌توان گزارشگیری نمود.
    هنوز کتابخانه google-gdata جهت استفاده از این API به روز رسانی نشده است؛ بنابراین در این مقاله سعی خواهیم کرد نحوه کار با این API را از صفر بازنویسی کنیم.
    مطابق صفحه تعاریف پروتکل، سه روش اعتبارسنجی جهت دریافت اطلاعات API معرفی شده است که در اینجا از روش ClientLogin که مرسوم‌تر است استفاده خواهیم کرد.
    مطابق مثالی که در آن صفحه قرار دارد، اطلاعاتی شبیه به اطلاعات زیر را باید ارسال و دریافت کنیم:

    POST /accounts/ClientLogin HTTP/1.1
    User-Agent: curl/7.15.1 (i486-pc-linux-gnu) libcurl/7.15.1
    OpenSSL/0.9.8a zlib/1.2.3 libidn/0.5.18
    Host: www.google.com
    Accept: */*
    Content-Length: 103
    Content-Type: application/x-www-form-urlencoded
    accountType=GOOGLE&Email=userName@google.com&Passwd=myPasswrd&source=curl-tester-1.0&service=analytics

    HTTP/1.1 200 OK
    Content-Type: text/plain
    Cache-control: no-cache
    Pragma: no-cache
    Date: Mon, 02 Jun 2008 22:08:51 GMT
    Content-Length: 497
    SID=DQ...
    LSID=DQAA...
    Auth=DQAAAG8...
    در دات نت فریم ورک، این‌کار را به صورت زیر می‌توان انجام داد:
            string getSecurityToken()
    {
    if (string.IsNullOrEmpty(Email))
    throw new NullReferenceException("Email is required!");

    if (string.IsNullOrEmpty(Password))
    throw new NullReferenceException("Password is required!");

    WebRequest request = WebRequest.Create("https://www.google.com/accounts/ClientLogin");
    request.Method = "POST";

    string postData = "accountType=GOOGLE&Email=" + Email + "&Passwd=" + Password + "&service=analytics&source=vahid-testapp-1.0";
    byte[] byteArray = Encoding.ASCII.GetBytes(postData);

    request.ContentType = "application/x-www-form-urlencoded";
    request.ContentLength = byteArray.Length;

    using (Stream dataSt = request.GetRequestStream())
    {
    dataSt.Write(byteArray, 0, byteArray.Length);
    }

    string auth = string.Empty;
    using (WebResponse response = request.GetResponse())
    {
    using (Stream dataStream = response.GetResponseStream())
    {
    using (StreamReader reader = new StreamReader(dataStream))
    {
    string responseFromServer = reader.ReadToEnd().Trim();
    string[] tokens = responseFromServer.Split('\n');
    foreach (string token in tokens)
    {
    if (token.StartsWith("SID="))
    continue;

    if (token.StartsWith("LSID="))
    continue;

    if (token.StartsWith("Auth="))
    {
    auth = token.Substring(5);
    }
    else
    {
    throw new AuthenticationException("Error authenticating Google user " + Email);
    }
    }
    }
    }
    }

    return auth;

    }

    همانطور که ملاحظه می‌کنید به آدرس https://www.google.com/accounts/ClientLogin ، اطلاعات postData با متد POST ارسال شده (دقیقا مطابق توضیحات گوگل) و سپس از پاسخ دریافتی، مقدار نشانه Auth را جدا نموده و در ادامه عملیات استفاده خواهیم کرد. وجود این نشانه در پاسخ دریافتی به معنای موفقیت آمیز بودن اعتبار سنجی ما است و مقدار آن در طول کل عملیات باید نگهداری شده و مورد استفاده مجدد قرار گیرد.
    سپس مطابق ادامه توضیحات API گوگل باید لیست پروفایل‌هایی را که ایجاد کرده‌ایم پیدا نمائیم:

    string getAvailableProfiles(string authToken)
    {
    return fetchPage("https://www.google.com/analytics/feeds/accounts/default", authToken);
    }

    متد fetchPage را از پیوست این مقاله می‌توانید دریافت نمائید. خروجی یک فایل xml است که با انواع و اقسام روش‌های موجود قابل آنالیز است، از کتابخانه‌های XML دات نت گرفته تا Linq to xml و یا روش serialization که من روش آخر را ترجیح می‌دهم.
    مرحله بعد، ساخت URL زیر و دریافت مجدد اطلاعات مربوطه است:
                string url = string.Format("https://www.google.com/analytics/feeds/data?ids={0}&metrics=ga:pageviews&start-date={1}&end-date={2}", id, from, to);
    return fetchPage(url, auth);
    و سپس آنالیز اطلاعات xml دریافتی، جهت استخراج تعداد بار مشاهده صفحات یا pageviews استفاده شده در این مثال. لیست کامل مواردی که قابل گزارشگیری است، در صفحه Dimensions & Metrics Reference گوگل ذکر شده است.

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

    مثالی در مورد نحوه استفاده از آن:
                CGoogleAnalytics cga = new CGoogleAnalytics
    {
    Email = "username@gmail.com",
    Password = "password",
    From = DateTime.Now.Subtract(TimeSpan.FromDays(1)),
    To = DateTime.Now.Subtract(TimeSpan.FromDays(1))
    };
    List<CGoogleAnalytics.SitePagePreviews> pagePreviews =
    cga.GetTotalNumberOfPageViews();

    foreach (var list in pagePreviews)
    {
    //string site = list.Site;
    //int pw = list.PagePreviews;
    }