مطالب
Globalization در ASP.NET MVC - قسمت سوم
قبل از ادامه، بهتر است یک مقدمه کوتاه درباره انواع منابع موجود در ASP.NET ارائه شود تا درک مطالب بعدی آسانتر شود.

نکات اولیه
- یک فایل Resource درواقع یک فایل XML شامل رشته هایی برای ذخیره سازی مقادیر (منابع) موردنیاز است. مثلا رشته هایی برای ترجمه به زبانهای دیگر، یا مسیرهایی برای یافتن تصاویر یا فایلها و ... . پسوند این فایلها resx. است (مثل MyResource.resx).
- این فایلها برای ذخیره منابع از جفت داده‌های کلید-مقدار (key-value pair) استفاده می‌کنند. هر کلید معرف یک ورودی مجزاست. نام این کلیدها حساس به حروف بزرگ و کوچک نیست (Not Case-Sensitive).
- برای هر زبان (مثل fa برای فارسی) یا کالچر موردنظر (مثل fa-IR برای فارسی ایرانی) می‌توان یک فایل Resource جداگانه تولید کرد. عناون زبان یا کالچر باید جزئی از نام فایل Resource مربوطه باشد (مثل MyResource.fa.resx یا MyResource.fa-IR.resx). هر منبع باید دارای یک فایل اصلی (پیش‌فرض) Resource باشد. این فایل، فایلی است که برای حالت پیش‌فرض برنامه (بدون کالچر) تهیه شده است و در عنوان آن از نام زیان یا کالچری استفاده نشده است (مثل MyResource.resx). برای اطلاعات بیشتر به قسمت اول این سری مراجعه کنید.
- تمامی فایل‌های Resource باید دارای کلیدهای یکسان با فایل اصلی Resource باشند. البته لزومی ندارد که این فایل‌ها حاوی تمامی کلیدهای منبع پیش‌فرض باشند. درصورت عدم وجود کلیدی در یک فایل Resource عملیات پیش فرض موجود در دات نت با استفاده از فرایند مشهور به fallback مقدار کلید موردنظر را از نزدیکترین و مناسبترین فایل موجود انتخاب می‌کند (درباره این رفتار در قسمت اول توضیحاتی ارائه شده است).
- در زمان اجرا موتور پیش فرض مدیریت منابع دات نت با توجه به کالچر UI در ثرد جاری اقدام به انتخاب مقدار مناسب برای کلیدهای درخواستی (به همراه فرایند fallback) می‌کند. فرایند نسبتا پیچیده fallback در اینجا شرح داده شده است.

منابع Global و Local
در ASP.NET دو نوع کلی Resource وجود دارد که هر کدام برای موقعیت‌های خاصی مورد استفاده قرار می‌گیرند:

- Resourceهای Global: منابعی کلی هستند که در تمام برنامه در دسترسند. این فایل‌ها در مسیر رزرو شده APP_GlobalResources در ریشه سایت قرار می‌گیرند. محتوای هر فایل resx. موجود در این فولدر دارای دسترسی کلی خواهد بود.

- Resourceهای Local: این منابع همان‌طور که از نامشان پیداست محلی! هستند و درواقع مخصوص همان مسیری هستند که در آن تعبیه شده اند! در استفاده از منابع محلی به ازای هر صفحه وب (aspx. یا master.) یا هر یوزرکنترل (ascx.) یک فایل resx. تولید می‌شود که تنها در همان صفحه یا یوزرکنترل در دسترس است. این فایل‌ها درون فولدر رزرو شده APP_LocalResources در مسیرهای موردنظر قرار می‌گیرند. درواقع در هر مسیری که نیاز به این نوع از منابع باشد، باید فولدری با عنوان App_LocalResources ایجاد شود و فایلهای resx. مرتبط با صفحه‌ها یا یوزرکنترل‌های آن مسیر در این فولدر مخصوص قرار گیرد.
در تصویر زیر چگونگی افزودن این فولدرهای مخصوص به پروژه وب اپلیکیشن نشان داده شده است:

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

نکته: البته با تغییر نام یک فولدر معمولی به این نام‌های رزرو شده نتیجه یکسانی بدست خواهد آمد.
 
نکته: در زمان اجرا، عملیات استخراج داده‌های موجود در این نوع منابع، به صورت خودکار توسط ASP.NET انجام می‌شود. این داده‌ها پس از استخراج در حافظه سرور کش خواهند شد.

برای روشن‌تر شدن مطالب اشاره شده در بالا به تصویر فرضی! زیر توجه کنید (اسمبلی‌های تولید شده برای منابع کلی و محلی فرضی است):

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

نکته: نحوه برخورد با این نوع از فایل‌های Resource در پروژه‌های Web Site و Web Application کمی باهم فرق می‌کند. موارد اشاره شده در این مطلب بیشتر درباره Web Applicationها صدق می‌کند.

برای آشنایی بیشتر بهتر است یک برنامه وب اپلیکیشن جدید ایجاد کرده و همانند تصویر زیر یکسری فایل Resource به فولدرهای اشاره شده در بالا اضافه کنید:

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

می‌بینید که خاصیت Build Action آن به Content مقداردهی شده است. این مقدار موجب می‌شود تا این فایل به همین صورت و در همین مسیر مستقیما در پابلیش نهایی برنامه ظاهر شود. در قسمت قبل به خاصیت Buil Action و مقادیر مختلف آن اشاره شده است.
هم‌چنین می‌بینید که مقدار پراپرتی Custom Tool به GlobalResourceProxyGenerator تنظیم شده است. این ابزار مخصوص تولید کلاس مربوط به منابع کلی در ویژوال استودیو است. با استفاده از این ابزار فایل Resource1.Designer.cs که در تصویر قبلی نیز نشان داده شده، تولید می‌شود.
حالا پنجره پراپرتی‌های منبع محلی را باز کنید:

می‌بینید که همانند منبع کلی خاصیت Build Action آن به Content تنظیم شده است. همچنین مقداری برای پراپرتی Custom Tool تنظیم نشده است. این مقدار پیش فرض را تغییر ندهید، چون با تنظیم مقداری برای آن چیز مفیدی عایدتان نمی‌شود! 

نکته: برای به روز رسانی مقادیر کلیدهای منابعی که با توجه به توضیحات بالا به همراه برنامه به صورت فایلهای resx. پابلیش می‌شوند، کافی است تا محتوای فایلهای resx. مربوطه با استفاده از یک ابزار (همانند نمونه ای که در قسمت قبل شرح داده شد) تغییر داده شوند. بقیه عملیات توسط ASP.NET انجام خواهد شد. اما با تغییر محتوای این فایلهای resx. با توجه به رفتار FCN در ASP.NET (که در قسمت قبل نیز توضیح داده شد) سایت Restart خواهد شد. البته این روش تنها برای منابع کلی و محلی درون مسیرهای مخصوص اشاره شده کار خواهد کرد.

استفاده از منابع Local و Global
پس از تولید فایل‌های Resource، می‌توان از آن‌ها در صفحات وب استفاده کرد. معمولا از این نوع منابع برای مقداردهی پراپرتی کنترل‌ها در صفحات وب استفاده می‌شود. برای استفاده از کلیدهای منابع محلی می‌توان از روشی همانند زیر بهره برد:
<asp:Label ID="lblLocal" runat="server" meta:resourcekey="lblLocalResources" ></asp:Label> 
اما برای منابع کلی تنها می‌توان از روش زیر استفاده کرد (یعنی برای منابع محلی نیز می‌توان از این روش استفاده کرد):
<asp:Label ID="lblGlobal" runat="server" Text="<%$ Resources:CommonTerms, HelloText %>" ></asp:Label> 
به این عبارات که با فوت پررنگ مشخص شده اند اصطلاحا «عبارات بومی‌سازی» (Localization Expression) می‌گویند. در ادامه این سری مطالب با نحوه تعریف نمونه‌های سفارشی آن آشنا خواهیم شد.
به نمونه اول که برای منابع محلی استفاده می‌شود نوع ضمنی (Implicit Localization Expression) می‌گویند. زیرا نیازی نیست تا محل کلید موردنظر صراحتا ذکر شود!
به نمونه دوم که برای منابع کلی استفاه می‌شود نوع صریح (Explicit Localization Expression) می‌گویند. زیرا برای یافتن کلید موردنظر باید آدرس دقیق آن ذکر شود!

بومی سازی ضمنی (Implicit Localization) با منابع محلی
عنوان کلید مربوطه در این نوع عبارات همانطور که در بالا نشان داده شده است، با استفاده از پراپرتی مخصوص meta:resoursekey مشخص می‌شود. در استفاده از منابع محلی تنها یک نام برای کل خواص کنترل مربوطه در صفحات وب کفایت می‌کند. زیرا عنوان کلیدهای این منبع باید از طرح زیر پیروی کند:
ResourceKey.Property
ResourceKey.Property.SubProperty    یا    ResourceKey.Property-SubProperty
برای مثال در لیبل بالا که نام کلید Resource آن به lblLocalResources تنظیم شده است، اگر نام صفحه وب مربوطه page1.aspx باشد، برای تنظیم خواص آن در فایل page1.aspx.resx مربوطه باید از کلیدهایی با عناوینی مثل عنوان‌های زیر استفاه کرد:
lblLocalResources.Text
lblLocalResources.BackColor
برای نمونه به تصاویر زیر دقت کنید:


بومی سازی صریح (Explicit Localization)
در استفاده از این نوع عبارات، پراپرتی مربوطه و نام فایل منبع صراحتا در تگ کنترل مربوطه آورده می‌شود. بنابراین برای هر خاصیتی که می‌خواهیم مقدار آن از منبعی خاص گرفته شود باید از عبارتی با طرح زیر استفاده کنیم:
<%$ Resources: Class, ResourceKey %>
در این عبارت، رشته Resources پیشوند (Prefix) نام دارد و مشخص کننده استفاده از نوع صریح عبارات بومی سازی است. Class نام کلاس مربوط به فایل منبع بوده و اختیاری است که تنها برای منابع کلی باید آورده شود. ResourceKey نیز کلید مربوطه را در فایل منبع مشخص می‌کند.
برای نمونه به تصاویر زیر دقت کنید:


نکته: استفاده همزمان از این دو نوع عبارت بومی سازی در یک کنترل مجاز نیست!

نکته: به دلیل تولید کلاسی مخصوص منابع کلی (با توجه به توضیحات ابتدای این مطلب راجع به پراپرتی Custom Tool)، امکان استفاده مستقیم از آن درون کد نیز وجود دارد. این کلاسها که به صورت خودکار تولید می‌شوند، به صورت مستقیم از کلاس ResourceManager برای یافتن کلیدهای منابع استفاده می‌کنند. اما روش مستقیمی برای استفاده از کلیدهای منابع محلی درون کد وجود ندارد. 

نکته: درون کلاس System.Web.UI.TemplateControl و نیز کلاس HttpContext دو متد با نامهای GetGlobalResourceObject و GetLocalResourceObject وجود دارد که برای یافتن کلیدهای منابع به صورت غیرمستقیم استفاده می‌شوند. مقدار برگشتی این دو متد از نوع object است. این دو متد به صورت مستقیم از کلاس ResourceManager استفاده نمیکنند! هم‌چنین ازآنجاکه کلاس Page از کلاس TemplateControl مشتق شده است، بنابراین این دو متد در صفحات وب در دسترس هستند.

دسترسی با برنامه نویسی
همانطور که در بالا اشاره شد امکان دستیابی به کلیدهای منابع محلی و کلی ازطریق دو متد GetGlobalResourceObject و GetLocalResourceObject نیز امکان پذیر است. این دو متد با فراخوانی ResourceProviderFactory جاری سعی در یافتن مقادیر کلیدهای درخواستی در منابع موجود می‌کنند. درباره این فرایند در مطالب بعدی به صورت مفصل بحث خواهد شد.

کلاس TemplateControl
این دو متد در کلاس TemplateControl از نوع Instance (غیر استاتیک) هستند. امضای (Signature) این دو متد در این کلاس به صورت زیر است:

متد GetLocalResourceObject:
protected object GetLocalResourceObject(string resourceKey)
protected object GetLocalResourceObject(string resourceKey, Type objType, string propName)
در متد اول، پارامتر resourceKey در متد GetLocalResourceObject معرف کلید منبع مربوطه در فایل منبع محلی متناظر با صفحه جاری است. مثلا lblLocalResources.Text. ازآنجاکه به صورت پیش‌فرض موقعیت فایل منبع محلی مرتبط با صفحات وب مشخص است بنابراین تنها ارائه کلید مربوطه برای یافتن مقدار آن کافی است. مثال:
txtTest.Text = GetLocalResourceObject("txtTest.Text") as string;
متد دوم برای استخراج کلیدهای منبع محلی با مشخص کردن نوع داده محتوا (معمولا برای داده‌های غیر رشته‌ای) و پراپرتی موردنظر به کار می‌رود. در این متد پارامتر objType برای معرفی نوع داده متناظر با داده موجود در کلید resourceKey استفاده می‌شود. از پارامتر propName نیز همانطور که از نامش پیداست برای مشخص کردن پراپرتی موردنظر از این نوع داده معرفی شده استفاده می‌شود.

متد GetGlobalResourceObject:
protected object GetGlobalResourceObject(string className, string resourceKey)
protected object GetGlobalResourceObject(string className, string resourceKey, Type objType, string propName)
در این دو متد، پارامتر className مشخص کننده نام کلاس متناظر با فایل منبع اصلی (فایل منبع اصلی که کلاس مربوطه با نام آن ساخته می‌شود) است. سایر پارامترها همانند دو متد قبلی است. مثال:
TextBox1.Text = GetGlobalResourceObject("Resource1", "String1") as string;

کلاس HttpContext
در این کلاس دو متد موردبحث از نوع استاتیک و به صورت زیر تعریف شده‌اند:

متد GetLocalResourceObject: 
public static object GetLocalResourceObject(string virtualPath, string resourceKey)
public static object GetLocalResourceObject(string virtualPath, string resourceKey, CultureInfo culture)
در این دو متد، پارامتر virtualPath مشخص کننده مسیر نسبی صفحه وب متناظر با فایل منبع محلی موردنظر است، مثل "Default.aspx/~". پارامتر resourceKey نیز کلید منبع را تعیین می‌کند و پارامتر culture نیز به کالچر موردنظر اشاره دارد. مثال:
txtTest.Text = HttpContext.GetLocalResourceObject("~/Default.aspx", "txtTest.Text") as string;
 
متد GetGlobalResourceObject:
public static object GetGlobalResourceObject(string classKey, string resourceKey)
public static object GetGlobalResourceObject(string classKey, string resourceKey, CultureInfo culture)
در این دو متد، پارامتر className مشخص کننده نام کلاس متناظر با فایل منبع اصلی (فایل منبع بدون نام زبان که کلاس مربوطه با نام آن ساخته می‌شود) است. سایر پارامترها همانند دو متد قبلی است. مثال:
TextBox1.Text = HttpContext.GetGlobalResourceObject("Resource1", "String1") as string;
 
نکته: بدیهی است که در MVC تنها می‌توان از متدهای کلاس HttpContext استفاده کرد.

روش دیگری که تنها برای منابع کلی در دسترس است، استفاده مستقیم از کلاسی است که به صورت خودکار توسط ابزارهای Visual Studio برای فایل منبع اصلی تولید می‌شود. نمونه‌ای از این کلاس را که برای یک فایل Resource1.resx (که تنها یک ورودی با نام String1 دارد) در پوشه App_GlobalResources تولید شده است، در زیر مشاهده می‌کنید:
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.17626
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Resources {
    using System;
    
    /// <summary>
    ///   A strongly-typed resource class, for looking up localized strings, etc.
    /// </summary>
    // This class was auto-generated by the StronglyTypedResourceBuilder
    // class via a tool like ResGen or Visual Studio.
    // To add or remove a member, edit your .ResX file then rerun ResGen
    // with the /str option or rebuild the Visual Studio project.
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Web.Application.StronglyTypedResourceProxyBuilder", "10.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    internal class Resource1 {
        
        private static global::System.Resources.ResourceManager resourceMan;
        
        private static global::System.Globalization.CultureInfo resourceCulture;
        
        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal Resource1() {
        }
        
        /// <summary>
        ///   Returns the cached ResourceManager instance used by this class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Resources.ResourceManager ResourceManager {
            get {
                if (object.ReferenceEquals(resourceMan, null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Resources.Resource1", global::System.Reflection.Assembly.Load("App_GlobalResources"));
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
        
        /// <summary>
        ///   Overrides the current thread's CurrentUICulture property for all
        ///   resource lookups using this strongly typed resource class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Globalization.CultureInfo Culture {
            get {
                return resourceCulture;
            }
            set {
                resourceCulture = value;
            }
        }
        
        /// <summary>
        ///   Looks up a localized string similar to String1.
        /// </summary>
        internal static string String1 {
            get {
                return ResourceManager.GetString("String1", resourceCulture);
            }
        }
    }
}

نکته: فضای نام پیش‌فرض برای منابع کلی در این کلاس‌ها همیشه Resources است که برابر پیشوند (Prefix) عبارت بومی سازی صریح است.

نکته: در کلاس بالا نحوه نمونه سازی کلاس ResourceManager نشان داده شده است. همانطور که مشاهده می‌کنید تعیین کردن مشخصات فایل اصلی Resource مربوطه که در اسمبلی نهایی تولید و کش می‌شود، اجباری است! در مطلب بعدی با این کلاس بیشتر آشنا خواهیم شد.

نکته: همانطور که قبلا نیز اشاره شد، کار تولید اسمبلی مربوط به فایل‌های منابع کلی و محلی و کش کردن آن‌ها در اسمبلی در زمان اجرا کاملا بر عهده ASP.NET است. مثلا در نمونه کد بالا می‌بینید که کلاس ResourceManager برای استخراج نوع Resources.Resource1 از اسمبلی App_GlobalResources نمونه‌سازی شده است، با اینکه این اسمبلی و نوع مذبور در زمان کامپایل و پابلیش وجود ندارد!

برای استفاده از این کلاس می‌توان به صورت زیر عمل کرد:
TextBox1.Text = Resources.Resource1.String1;

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

نکات نهایی
حال که با مفاهیم کلی بیشتری آشنا شدیم بهتر است کمی هم به نکات ریزتر بپردازیم:

نکته: فایل تولیدی توسط ویژوال استودیو در فرایند مدیریت منابع ASP.NET تاثیرگذار نیست! باز هم تاکید می‌کنم که کار استخراج کلیدهای Resource از درون فایلهای resx. کاملا به صورت جداگانه و خودکار و در زمان اجرا انجام می‌شود (درباره این فرایند در مطالب بعدی شرح مفصلی خواهد آمد). درواقع شما می‌توانید خاصیت Custom Tool مربوط به منابع کلی را نیز همانند منابع محلی به رشته‌ای خالی مقداردهی کنید و ببینید که خللی در فرایند مربوطه رخ نخواهد داد!

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

نکته: اگر فایل‌های Resource درون اسمبلی‌های جداگانه‌ای باشند (مثلا در یک پروژه جداگانه، همانطور که در قسمت اول این سری مطالب پیشنهاد شده است)، موتور پیش فرض منابع در ASP.NET بدرد نخواهد خورد! بنابراین یا باید از نمونه‌های اختصاصی کلاس ResourceManager استفاده کرد (کاری که کلاس‌های خودکار تولیدشده توسط ابزارهای ویژوال استودیو انجام می‌دهند)، یا باید از پرووایدرهای سفارشی استفاده کرد که در مطالب بعدی نحوه تولید آن‌ها شرح داده خواهد شد.
 
همانطور که در ابتدای این مطلب اشاره شد، این مقدمه در اینجا صرفا برای آشنایی بیشتر با این دونوع Resource آورده شده تا ادامه مطلب روشن‌تر باشد، زیرا با توجه به مطالب ارائه شده در قسمت اول این سری، در پروژه‌های MVC استفاده از یک پروژه جداگانه برای نگهداری این منابع راه حل مناسبتری است.
در مطلب بعدی به شرح نحوه تولید پرووایدرهای سفارشی می‌پردازم.
مطالب
برنامه ریزی به روش چابک

شاید شما هم مثل من فکر می‌کنید، به اندازه‌ای که درحرفه یا زندگی شخصی خود زحمت می‌کشید، نتیجه نمی‌گیرید! چندی پیش کتابی خواندم از آقای J.D Meier  (مهندس نرم افزار و مدیر پروژه در شرکت مایکروسافت) که نحوه‌ی برنامه ریزی و زمان بندی من در کار و زندگی ام را به طور شگفت آوری متحول کرد. به همین دلیل به این فکر افتادم که در قالب چند مقاله به معرفی روش ایشان که یک روش بسیار آسان برای گرفتن نتایج بهتر و سریع‌تر در کار و زندگی است، بپردازم. امیدوارم همانطور که من این روش را مفید و کاربردی یافتم، برای خوانندگان این مقاله هم تأثیرگزار باشد.

این مدل که روش چابک ( Agile way ) نام دارد برپایه چهار اصل اساسی  به نام‌های قانون سه تایی، دیدگاه شنبه، خروجی روزانه و بازخورد پنجشنبه شکل گرفته است که درادامه به توضیح هریک از این اصول پرداخته شده است.

1)  قانون سه تایی:

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

قانون سه تایی یعنی به صورت سه تایی فکر کردن. به این معنی که شما سه نتیجه‌ای را که مایل هستید در انتهای یک روز، یک هفته یا یک ماه بدست آورید را مشخص می‌کنید. با این سیستم  سه در سه (3 x 3) شما نقشه آنچه را که می‌خواهید به دست آورید، به صورت خیلی ساده و سریع در مجموعه‌های سه تایی طراحی می‌کنید که به راحتی قابل بخاطر سپردن و ویرایش سریع است.

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

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

2) دیدگاه شنبه

  در این مدل در روز شنبه (اولین روز هفته) سه نتیجه‌ای را که مایل هستید در آخر هفته به دست بیاورید را مشخص می‌کنید. به طور مثال شما می‌خواهید طراحی اسلایدهای مربوط به جلسه‌ی هفته آینده را تا آخر هفته جاری به اتمام برسانید و یا مایل هستید که پس از یک هفته تمرین، به راحتی بتوانید دو مایل را در روز بدوید.

3) خروجی روزانه

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

4) بازتاب پنجشنبه

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

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

نقاط جوش

در جدول برنامه ریزی مدل چابک ستونی به نام نقاط جوش ( hot spots ) گنجانده شده است. اگر مسایل مهم زندگی خود را در نظر بگیرید، احتمالاً آنها جزء یکی از مسایل مربوط به تفکرات و یا جسم شما، مسایل احساسی ویا مالی، معاشرت با دیگران و تفریحات خواهند بود. نقاط جوش زندگی در حقیقت بخش هایی از زندگی شما هستند که شما بر روی آنها تمرکز بیشتری دارید و به طور ساده آنها می‌توانند بیانگر درد و رنج فراوان، یا موفقیت‌ها و موقعیت‌های فراوان برای شما باشند. نکته‌ی مهم این است که این نقاط جوش هرچه باشند، بخش‌های مهمی در زندگی شما، از نظر شما هستند.

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

خلاصه‌ی مطلب

1) قانون سه تایی به عنوان یک روش ساده برای جلوگیری از بی نظمی و هرج ومرج و به سرانجام رساندن کارهای موردنظر با کیفیت بالا در مدت زمان مناسب به کار می‌رود.

2) ابتدا سه نقطه‌ی جوش را در زندگی خود انتخاب کنید. سه نتیجه‌ی کلی و نهایی را برای هریک، براساس اولویت‌ها و اهمیت آنها مشخص کنید. به طور مثال این سوال را از خود بپرسید که دوست دارید در سال آینده در هر یک از این بخشهای زندگی خود، چه دستاوردی را داشته باشید.

3) انتخاب سه دستاورد دلخواه را به صورت ماهیانه، هفتگی و روزانه تکرار کنید. توجه کنید که خروجی هر روز باید در راستای خروجی هفته و خروجی هر هفته در راستای خروجی هر ماه، باشد.

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

در مقالات آینده درباره اینکه چطور به صورت بهینه و کارآمد دیدگاه‌های هفتگی و خروجی‌های روزانه را مشخص و برنامه ریزی کنیم صحبت خواهیم کرد. 
مطالب
استفاده از Re-Captcha
در اینجا استفاده از re-CAPTCHA برای ASP.Net و در اینجا برای ASP.Net MVC با استفاده از سرویس گوگل نسخه 1 آن آشنا شدید. در این مقاله می‌خواهیم توضیحاتی را در مورد دلیل استفاده و نحوه‌ی ثبت re-CAPTCHA نسخه 2 برای تکنولوژی‌های ASP.Net و ASP.Net MVC ارائه کنیم.

  reCAPTCHA چیست؟

استفاده آسان و امنیت بالا، جمله‌ای می‌باشد که گوگل در سرتیتر تعریف آن جای داده که البته عنوان «من روبات نیستم» در سرویس استفاده شده‌است. reCAPTCHA یک سرویس رایگان برای وب سایت‌های شما در جهت حفظ آن در برابر روبات‌های مخرب است و از موتور تجزیه و تحلیل پیشرفته‌ی تشخیص انسان در برابر روبات‌ها استفاده می‌نماید. reCAPTCHA را میتوان به صورت ماژول در بلاگ و یا فرم‌های ثبت نام و ... جای داد که فقط با یک کلیک هویت سنجی انجام خواهد شد. گاها ممکن است بجای کلیک از شما سوالی پرسیده شود که در این صورت می‌بایستی تصاویر مرتبط با آن سوال را تیک زده باشید.



دلیل استفاده از reCAPTCHA:

  1. گزارش روزانه از وضعیت موفقیت آمیز بودن هویت سنجی
  2. سهولت استفاده برای کاربران
  3. سهولت استفاده جهت برنامه نویسان
  4. دسترسی پذیری مناسب بدلیل وجود سؤالات تصویری و تلفظ و پخش عبارت بصورت صوتی
  5. امنیت بالا 

آیا می‌توان قالب reCAPTCHA را تغییر داد؟

جواب این سوال بله می‌باشد. این سرویس در دو قالب سفید و مشکی ارائه شده‌است که به صورت پیش فرض قالب سفید آن انتخاب می‌شود. در تصویر زیر قالب‌های این سرویس را مشاهده خواهید کرد.



زبان‌های پشتیبانی شده در این سرویس:


اضافه نمودن reCAPTCHA به سایت:

اگر قبلا در گوگل ثبت نام نموده‌اید کافیست وارد این سایت شوید و بر روی Get reCAPTCHA کلیک نمائید؛ در غیر اینصورت می‌بایستی یک حساب کاربری ایجاد نماید. بعد از ورود، به کنترل پنل هدایت خواهید شد. در نمای اول به تصویر زیر برخورد خواهید کرد که از شما ثبت سایت جدید را خواستار است:



نام، دامنه سایت و مالک را وارد و ثبت نام نماید.

پس از آنکه بر روی دکمه‌ی ثبت نام کلیک نمودید، برای شما دو کلید جدید را ثبت می‌نماید که منحصر به سایت شماست. Site Key رشته ای را داراست که در کد‌های HTML قرار خواهد گرفت و کلید بعدی Secret Key می‌باشد. ارتباط سایت شما با گوگل می‌بایستی به صورت محرمانه محفوظ بماند.


گام‌های لازم جهت نمایش سرویس در سایت:

  1. دستورات سمت کاربر
  2. دستورات سمت سرور 

دستورات سمت کاربر:

کد زیر را در قبل از بسته شدن تک <head/> قرار دهید:

<script src='https://www.google.com/recaptcha/api.js'></script>
کد زیر را در داخل تگ فرمی که می‌خواهید کپچا نمایش داده شود قرار دهید:
<div data-sitekey="6LdHGgwTAAAAAClKFhGthRrjBXh5AUGd4eWNCQq7"></div>

نکته: مقدار data-sitekey برابر است با رشته Site Key که گوگل برای شما ثبت نمود.



دستورات سمت سرور:

وقتی کاربر فرم حاوی کپچا را که به صورت صحیح هویت سنجی آن انجام شده باشد به سمت سرور ارسال کند، به عنوان بخشی از داده‌ی ارسال شده، یک رشته با نام g-recaptcha-response  با دستور Request دریافت خواهید کرد که به منظور بررسی اینکه آیا گوگل تایید کرده است که کاربر، یک درخواست POST ارسال نمود‌است. با این پارامترها یک مقدار json برگشت داده خواهد شد که می‌بایستی کلاسی متناظر با آن جهت خواندن ساخته شود.

با استفاده از کد زیر مقدار برگشتی Json را در کلاس مپ می‌نمائیم:
using System.Collections.Generic;
using Newtonsoft.Json;

namespace BaseConfig.Security.Captcha
{
    public class RepaptchaResponse
    {
        [JsonProperty("success")]
        public bool Success { get; set; }

        [JsonProperty("error-codes")]
        public List<string> ErrorCodes { get; set; }
    }
}

با استفاده از کلاس زیر درخواستی به گوگل ارسال شده و در صورتیکه با خطا مواجه شود با استفاده از دستور switch به آن دسترسی خواهیم یافت.
using System.Configuration;
using System.Net;
using Newtonsoft.Json;

namespace BaseConfig.Security.Captcha
{
    public class ReCaptcha
    {
        public static string _secret;

        static ReCaptcha()
        {
            _secret = ConfigurationManager.AppSettings["ReCaptchaGoogleSecretKey"];
        }

        public static bool IsValid(string response)
        {
            //secret that was generated in key value pair
            var client = new WebClient();
            var reply = client.DownloadString($"https://www.google.com/recaptcha/api/siteverify?secret={_secret}&response={response}");

            var captchaResponse = JsonConvert.DeserializeObject<RepaptchaResponse>(reply);

            // when response is false check for the error message
            if (!captchaResponse.Success)
            {
                //if (captchaResponse.ErrorCodes.Count <= 0) return View();

                //var error = captchaResponse.ErrorCodes[0].ToLower();
                //switch (error)
                //{
                //    case ("missing-input-secret"):
                //        ViewBag.Message = "The secret parameter is missing.";
                //        break;
                //    case ("invalid-input-secret"):
                //        ViewBag.Message = "The secret parameter is invalid or malformed.";
                //        break;

                //    case ("missing-input-response"):
                //        ViewBag.Message = "The response parameter is missing.";
                //        break;
                //    case ("invalid-input-response"):
                //        ViewBag.Message = "The response parameter is invalid or malformed.";
                //        break;

                //    default:
                //        ViewBag.Message = "Error occured. Please try again";
                //        break;
                //}
                return false;
            }
            // Captcha is valid
            return true;
        }
    }
}

تابع IsValid از نوع برگشتی Boolean بوده و خطایی برگشت داده نخواهد شد و از این جهت به صورت کامنت برای شما گذاشته شده که می‌توان متناظر با کد نویسی آن را تغییر دهید.
در اکشن زیر مقدار response برسی می‌شود تا خالی نباشد و همچنین مقدار آن را می‌توان با استفاده از تابع IsValid در کلاس ReCaptcha به سمت گوگل فرستاد.
        //
        // POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public virtual async Task<ActionResult> Login(LoginPageModel model, string returnUrl)
        {
            var response = Request["g-recaptcha-response"];
            if (response != null && ReCaptcha.IsValid(response))
            {
                // 
            }
         }

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

/**
 * 
 * @param {} data 
 * @returns {} 
 */
function Success(data) {
    grecaptcha.reset();
}

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

برای این کار دستور
<div data-sitekey="6LdHGgwTAAAAAClKFhGthRrjBXh5AUGd4eWNCQq7"></div>  
که در بالا تعریف شد، به شکل زیر تغییر خواهد کرد:

    <script>
        var recaptcha1;
        var recaptcha2;
        var myCallBack = function () {
            //Render the recaptcha1 on the element with ID "recaptcha1"
            recaptcha1 = grecaptcha.render('recaptcha1', {
                'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9',
                'theme': 'light'
            });

            //Render the recaptcha2 on the element with ID "recaptcha2"
            recaptcha2 = grecaptcha.render('recaptcha2', {
                'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9',
                'theme': 'light'
            });

            //Render the recaptcha3 on the element with ID "recaptcha3"
            recaptcha2 = grecaptcha.render('recaptcha3', {
                'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9',
                'theme': 'light'
            });
        };
    </script>

برای نمایش کپچا، تگ‌های div با id متناظر با recaptcha1, recaptcha2, recaptcha3 ( در این مثال از سه کپچا در صفحه استفاده شده است ) در صفحه قرار خواهند گرفت.

<div id="recaptcha1"></div>
<div id="recaptcha2"></div>
<div id="recaptcha3"></div>

کار ما تمام شد. حال اگر پروژه را اجرا نمائید، در صفحه سه کپچا مشاهده خواهید کرد.


چند زبانه کردن کپچا:

برای چند زبانه کردن کافیست با مراجعه به این لینک و یا استفاده از تصویر بالا ( زبان‌های پشتیبانی ) مقدار آن زبان را برابر با پراپرتی hl که به صورت کوئری استرینگ برای گوگل ارسال می‌گردد، استفاده نمود. کد زیر نمونه‌ی استفاده شده برای زبان‌های انگلیسی و فارسی می‌باشد که با ریسورس مقدار دهی می‌شود.
<script src='https://www.google.com/recaptcha/api.js?hl=@(App_GlobalResources.CP.CurrentAbbrivation)'></script>

در صورتی که از فایل ریسوس استفاده نمی‌کنید می‌توان به صورت مستقیم مقدار دهی نمائید:
<script src='https://www.google.com/recaptcha/api.js?hl=fa'></script>



برای دوستانی که با تکنولوژی ASP.Net کار می‌کنند، این روال هم برای آنها هم صادق می‌باشد.

برای دریافت پروژه اینجا کلیک نمائید.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
یک نکته‌ی تکمیلی: چگونه پس از نصب SDK 3x جدید، بتوانیم همان پروژه‌ی قبلی را بدون به روز رسانی یا هیچگونه تغییری در آن، باز هم استفاده کنیم؟

با توجه به اینکه امضای یکسری از اینترفیس‌های نگارش 3x با 2x یکی نیست (مانند ILookupNormalizer)، پس از نصب SDK 3x، دیگر قادر به اجرای برنامه‌های 2x خود نخواهید شد؛ چون پروژه‌های NET Core. همواره از آخرین نگارش SDK نصب شده استفاده می‌کنند. برای قفل کردن شماره SDK یک Solution به نگارش 2x به صورت زیر عمل کنید:
dotnet --list-sdks
dotnet new globaljson --sdk-version 2.2.106
دستور اول لیست SDKهای نصب شده را نمایش می‌دهد و دستور دوم یک فایل global.json جدید را بر اساس شماره‌ای که از طریق اجرای دستور اول یافته‌اید، تولید می‌کند. این فایل باید در ریشه‌ی Solution قرار گیرد.
نظرات مطالب
توزیع پروژه‌های ASP.NET Core 1.1 بدون ارائه فایل‌های View آن
به روز رسانی
با حذف فایل project.json در VS 2017، اکنون با کلیک راست بر روی گروه نام پروژه (فایل csproj)، گزینه‌ی Edit آن ظاهر شده و مداخل ذکر شده‌ی در مطلب فوق، چنین تعاریفی را پیدا می‌کنند: 
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>
    <PreserveCompilationContext>true</PreserveCompilationContext>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="1.1.0">
      <PrivateAssets>All</PrivateAssets>
    </PackageReference>
  </ItemGroup>
</Project>
مطالب
شروع کار با Apache Cordova در ویژوال استودیو #3
در قسمت قبل توانستیم ابزارهای لازم را برای Apache Cordova، نصب کنیم. در این قسمت یک پروژه‌ی ساده را ایجاد کرده و در مورد ساختار آن توضیح خواهم داد. در ادامه‌ی مقالات از  AngularJS ، Bootstrap ,Typescript و jQuery Mobile  هم در پروژه‌ها استفاده خوهیم کرد.
برای شروع، از قسمت JavaScript یا Typescript، یک پروژه‌ی از نوع Blank App ایجاد کنید. به شکل زیر:


ترجیحا نوع Typescript را انتخاب کردم. البته در داخل فایل ts. امکان نوشتن جاوا اسکریپت هم هست. بعد از ایجاد پروژه اگر با تصویری شبیه به تصویر زیر روبرو شدید، در نتیجه تنظیمات نصب و راه اندازی به درستی صورت گرفته است.



اگر به قسمت solution explorer دقت کنید، فایلی به نام config.xml را مشاهده خواهید کرد. با کلیک بر روی این فایل، یک صفحه‌ی گرافیکی باز خواهد شد که این امکان را به شما می‌دهد که پلاگین‌های مورد نیاز خود، تنظیمات مربوط به نرم افزار تولیدی (مانند تنظیم ورژن ویندوزی که می‌خواهید app شما بر روی آن اجرا شود) و تنظیمات مربوط به هر یک از پلتفرم‌ها را به صورت مجزا در اختیار داشته باشید.



یک فایل index.html هم در قالب پیش‌فرض قرار داده شده که بعدا می‌توانید آن را تغییر دهید و یا صفحات دیگری را اضافه کنید. همان طور که در قسمت‌های قبل گفته شد، قرار است ما یک وب اپلیکیشن طراحی کنیم و آن را درون Container بومی Cordova بسته بندی کنیم. لذا محدودیتی برای استفاده‌ی از کتابخانه‌های مرتبط با CSS ، HTML و JavaScript  نداریم و در ادامه‌ی مقالات با مثال‌های متعددی از آن‌ها استفاده خواهیم کرد.

در فولدر scripts-->typeings-->cordova-->plugins  اینترفیس‌هایی که برای دسترسی به امکانات بومی دستگاه تلفن فعلا در Cordova پشتیبانی می‌شوند، قرار گرفته است.

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


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


برای یکپارچه شدن API‌های Cordova با وب پیج موجود، اندکی کد نیاز داریم که برای انکار لینکی شبیه لینک زیر را در فایل html خود استفاده می‌کنیم که فقط بعد از کامپایل وجود خارجی دارد؛ به صورت زیر:

<script src="cordova.js"></script>

در پایان هم برای فهمیدن اینکه API‌های Cordova در دسترس هستند، می‌توانیم رخداد مربوط به devicerady را مدیریت کنیم؛ به صورت زیر:

document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() { /* INIT */ }

برای مدیریت رخدادهای مربوط به pause و resume هم که نشان دهنده‌ی ادامه برنامه (خارج شدن از حالت pause) و حالت تعلیق هستند، می‌توان به شکل زیر عمل کرد:

 function onDeviceReady() {
            // Handle the Cordova pause and resume events
            document.addEventListener('pause', onPause, false);
            document.addEventListener('resume', onResume, false);

            // TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
        }

        function onPause() {
            // TODO: This application has been suspended. Save application state here.
        }

        function onResume() {
            // TODO: This application has been reactivated. Restore application state here.
        } 

حال قصد داریم پروژه‌ی خود را که قرار است یک متن ساده را نشان دهد، با استفاده از شبیه ساز اجر ا کنیم. برای این منظور از قسمت toolbar ویژوال استودیو ، Solution Platform خود را انتخاب کنید و سپس می‌توانید شبیه ساز مورد نظر خود را انتخاب کرده و برنامه را اجرا کنید. در اینجا محیط مورد نظر من اندروید است  و برای این منظور هم میتوانم از شبیه ساز Android Emulator یا Ripple استفاده کنم.  به دلیل سرعت کم شبیه ساز اندروید، می‌توانید شبیه ساز  YouWave را دانلود و اجرا کرده و در قسمتی که شبیه ساز را از toolbar ویژوال انتخاب می‌کردید، این بار گزینه‌ی Device را انتخاب کنید. بعد از کامپایل برنامه‌ی شما، فایل apk تولید شده بر روی شبیه ساز نصب خواهد شد و شما قادر خواهید بود آنرا اجرا کنید.

نتیجه‌ی نهایی 

 با شبیه ساز Ripple 


مطالعه بیشتر

https://msdn.microsoft.com/en-us/library/dn879821(v=vs.140).aspx 

http://blog.falafel.com/getting-started-with-cordova-and-multi-device-hybrid-app-in-visual-studio/ 

http://www.codeproject.com/Articles/860150/Visual-Studio-and-Apache-Cordova 


نکته : وقتی پروژه را برای اولین بار اجرا می‌کنید شاید کمی طول بکشد تا نتیجه‌ی نهایی را ببنید و آن هم به دلیل این است که ویژوال استودیو  باید مجموعه‌ای از package  های مورد نیاز Cordova را دانلود کند.

در مقاله بعد با jQuery Mobile آشنا خواهیم شد و یک مثال برای کار کردن با آن در نظر خواهم گرفت.


ادامه دارد ...

نظرات مطالب
استفاده از LocalDb در IIS، قسمت دوم: مالکیت وهله ها

SQL Server CE داخل پروسه برنامه اجرا میشه (و پروسه مجزایی نداره). نسخه LocalDB خارج از پروسه برنامه اما به صورت یک child process اجرا میشه. برنامه وب شما می‌تونه با هر تعداد فایل sdf مربوط به SQL CE یا فایل mdf بانک اطلاعاتی  LocalDB کار کنه (در رشته اتصالی آن AttachDbFileName قابل تعریف هست).

مطالب
ارتقاء به HTTP Client در Angular 4.3
عموما در برنامه‌های SPA، اطلاعات از طریق HTTP و از طرف سرور دریافت می‌شوند. از نگارش‌های ابتدایی Angular، اینکار از طریق HTTP Module آن مسیر بود و هست. در Angular 4.3 روش بهبودیافته‌ای نسبت به این روش متداول ارائه شده‌است که در ادامه تعدادی از ویژگی‌های مقدماتی آن‌را بررسی می‌کنیم.
هرچند ارتقاء به HttpClient الزامی نیست و کدهای پیشین، هنوز هم به خوبی کار می‌کنند؛ اما طراحی جدید آن شامل ویژگی‌های توکاری است که به سختی توسط HTTP Module پیشین قابل پیاده سازی هستند.


به روز رسانی وابستگی‌های پروژه

پیش از هرکاری نیاز است وابستگی‌های پروژه را به روز رسانی کرد و یکی از روش‌های ساده‌ی یافتن شماره نگارش‌های جدید بسته‌های تعریف شده‌ی در فایل package.json برنامه، استفاده از بسته‌ی npm-check-updates است:
npm install npm-check-updates -g
ncu
دستور اول، این بسته را به صورت عمومی نصب کرده و صدور دستور دوم در ریشه‌ی پروژه، سبب می‌شود تا گزارشی از آخرین به روز رسانی‌ها، نمایش داده شود (بدون هیچگونه تغییری در پروژه):


در اینجا شماره نگارش‌های جدید مشخص شده‌اند و همچنین روش سریع ارتقاء به آن‌ها نیز ذکر شده‌است. فقط کافی است دستورات ذیل را صادر کنیم تا این به روز رسانی‌ها توسط ncu انجام شوند:
ncu -a
npm update
دستور اول صرفا شماره نگارش‌های جدید را در فایل package.json، به صورت خودکار اصلاح می‌کند و دستور دوم سبب دریافت، نصب و اعمال آن‌ها خواهد گردید.


تغییرات مورد نیاز جهت معرفی ماژول HttpClient

این ماژول جدید از طریق اینترفیس HttpClientModule ارائه می‌شود. بنابراین اولین تغییر در جهت ارتقاء به نگارش 4.3، اصلاح importهای لازم است:
از:
 import { HttpModule } from '@angular/http';
به:
 import { HttpClientModule } from '@angular/common/http';

پس از آن، این HttpClientModule را به لیست imports ماژول اصلی برنامه اضافه می‌کنیم؛ تا در کل برنامه قابل دسترسی شود:
@NgModule({
    imports: [
        // ...
        HttpClientModule,
        // ...
    ],
declarations: [ ... ],
providers: [ ... ],
exports: [ ... ]
})
export class AppModule { }


تغییرات مورد نیاز در سازنده‌ها و تزریق وابستگی‌ها

پس از تغییرات فوق، دیگر دسترسی به HttpModule پیشین را نداریم. بنابراین نیاز است هر جائی را که سرویس Http به سازنده‌ی کلاسی تزریق شده‌است، یافته و به صورت ذیل تغییر دهیم:
از:
 constructor(private http: Http) { }
به:
import { HttpClient } from '@angular/common/http';
// ...
constructor(private http: HttpClient) { }


تغییرات مورد نیاز در کدهای سرویس‌ها جهت کار با HTTP Verbs

یکی از اهداف HTTP Client جدید، سادگی کار با اطلاعات دریافتی از سرور است. برای مثال در HTTP Module پیشین، روش دریافت اطلاعات از سرور به صورت ذیل است:
public get(): Observable<MyType> => {
    return this.http.get(url)
        .map((response: Response) => <MyType>response.json());
}
ابتدا درخواستی ارسال شده و سپس نتیجه‌ی آن به JSON تبدیل گشته و در آخر به نوع بازگشتی متد تبدیل می‌شود.
در HTTP Client جدید دیگر نیازی نیست تا متد ()json. فراخوانی شود. در اینجا به صورت پیش‌فرض نوع بازگشتی از سرور JSON فرض می‌شود. همچنین اکنون متدهای get/put/post و امثال آن برخلاف HTTP Client قبلی، جنریک هستند. یعنی در همینجا می‌توان نوع بازگشتی را هم مشخص کرد. به این ترتیب، قطعه کد قدیمی فوق، به کد ساده‌ی ذیل تبدیل می‌شود که در آن خبری از map و همچنین یک cast اضافی نیست:
get<T>(url: string): Observable<T> {
    return this.http.get<T>(url);
}
برای نمونه شبیه به همین نکته برای post نیز صادق است:
post<T>(url: string, body: string): Observable<T> {
   return this.http.post<T>(url, body);
}

نکته 1: در اینجا اگر خروجی از سرور، نوع دیگری را داشت، نیاز است responseType را به صورت صریحی به شکل ذیل مشخص کرد:
 getData() {
  this.http.get(this.url, { responseType: 'text' }).subscribe(res => {
       this.data = res;
  });
}
در این‌حالت خروجی متنی <Observable<string را دریافت می‌کنیم و نیازی به ذکر <get<string نیست.

نکته 2: ممکن است اطلاعات بازگشتی از سمت سرور، داخل یک فیلد محصور شده باشند:
{
  "results": [
    "Item 1",
    "Item 2",
  ]
}
در این حالت برای دسترسی به اطلاعات این فیلد می‌توان از حالت key/value بودن اشیاء جاوا اسکریپتی به شکل زیر برای دسترسی به خاصیت results استفاده کرد:
this.http.get('/api/items').subscribe(data => {
   this.results = data['results'];
});


نکاتی را که باید حین کار با یک RxJS Observable-based API در نظر داشت

این API جدید نیز همانند قبل مبتنی بر RxJS Observables است. بنابراین نکات ذیل در مورد آن نیز صادق است:
- اگر متد subscribe بر روی این observables فراخوانی نشود، اتفاقی رخ نخواهد داد.
- اگر چندین بار مشترک این observables شویم، چندین درخواست HTTP صادر می‌شوند.
- این نوع خاص از observables، تنها یک مقدار را بازگشت می‌دهند. اگر درخواست HTTP موفقیت آمیز باشد، این observables یک نتیجه را بازگشت داده و سپس خاتمه پیدا می‌کنند.
- این observables اگر در حین درخواست HTTP با خطایی مواجه شوند، سبب صدور استثنایی می‌شوند.


تغییرات مورد نیاز در کدهای سرویس‌ها جهت کار با HTTP Headers

در اینجا برای تعریف headers می‌توان به صورت ذیل عمل کرد:
import { HttpHeaders } from "@angular/common/http";

const headers = new HttpHeaders({ "Content-Type": "application/json" });
و یا به صورت fluent به شکل زیر:
 const headers = new HttpHeaders().set("Accept", "application/json").set('Content-Type', 'application/json');

سپس آن‌را به عنوان پارامتر سوم، به متدهای http ارسال می‌کنیم. یک مثال:
  updateAppProduct(id: number, item: AppProduct): Observable<AppProduct> {
    const header = new HttpHeaders({ "Content-Type": "application/json" });
    return this.http
      .put<AppProduct>(
        `${this.baseUrl}/UpdateProduct/${id}`,
        JSON.stringify(item),
        { headers: header }
      )
      .map(response => response || {});
  }

تعریف پارامتر options اینبار به صورت یک شیء دارای چندین خاصیت درآمده‌است. به همین جهت است که در اینجا یک {} را نیز مشاهده می‌کنید:
(method) HttpClient.post(url: string, body: any, options?: {
          headers?: HttpHeaders;
          observe?: "body";
          params?: HttpParams;
          reportProgress?: boolean;
          responseType?: "json";
          withCredentials?: boolean;
}): Observable<Object>

یک نکته: شیء HttpHeaders به صورت immutable طراحی شده‌است. یعنی اگر آن‌را به صورت ذیل فراخوانی کنیم:
const headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/json');
headers = headers.set('Accept', 'application/json');
headers تولیدی ... خالی خواهد بود. به همین جهت روش صحیح تشکیل آن به صورت ذیل و زنجیروار است:
 const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
;


امکان تعریف HttpParams

اگر به شیء options در تعریف فوق دقت کنید، دارای خاصیت اختیاری params نیز هست. از آن می‌توان جهت تعریف کوئری استرینگ‌ها استفاده کرد. برای مثال درخواست ذیل:
http
  .post('/api/items/add', body, {
      params: new HttpParams().set('id', '3'),
  })
  .subscribe();
سبب تولید یک چنین URL ایی می‌گردد:
  /api/items/add?id=3

یک نکته: شیء HttpParams به صورت immutable طراحی شده‌است. یعنی اگر آن‌را به صورت ذیل فراخوانی کنیم:
const params = new HttpParams();
params.set('orderBy', '"$key"')
params.set('limitToFirst', "1");
params تولیدی ... خالی خواهد بود. به همین جهت روش صحیح تشکیل آن به صورت ذیل و زنجیروار است:
const params = new HttpParams()
.set('orderBy', '"$key"')
.set('limitToFirst', "1");
به علاوه روش تعریف ذیل نیز برای کار با HttpParams مجاز است:
 const params = new HttpParams({fromString: 'orderBy="$key"&limitToFirst=1'});


تغییرات مورد نیاز در کدهای سرویس‌ها جهت مدیریت خطاها

در اینجا اینبار خطای بازگشتی، از نوع ویژه‌ی HttpErrorResponse است که شامل اطلاعات شماره کد و متن خطای حاصل می‌باشد:
import { HttpClient, HttpHeaders, HttpErrorResponse } from "@angular/common/http";

postData() { 
  this.http.post(this.url, this.payload).subscribe( 
    res => { 
      console.log(res); 
    }, 
    (err: HttpErrorResponse) => { 
      console.log(err.error); 
      console.log(err.name); 
      console.log(err.message); 
      console.log(err.status); 

        if (err.error instanceof Error) { 
          console.log("Client-side error occured."); 
        } else { 
          console.log("Server-side error occured."); 
        }
    } 
  ); 
}


امکان سعی مجدد در اتصال توسط HTTP Client

ممکن است در اولین سعی در اتصال به سرور، خطایی رخ دهد و یا سرور در دسترس نباشد. در اینجا توسط متد retry می‌توان درخواست سعی مجدد در اتصال را صادر کرد.
برای این منظور ابتدا عملگر retry مربوط به RxJS را import می‌کنیم:
 import 'rxjs/add/operator/retry';
سپس:
http
  .get<ItemsResponse>('/api/items')
  .retry(3)
  .subscribe(...);
این کد در صورت بروز خطایی، این عملیات را سه بار تکرار می‌کند. در انتها اگر بازهم خطایی دریافت شد، این خطا را به برنامه بازگشت می‌دهد.


امکان درخواست کل Response بجای Body

اگر به امضای پارامتر اختیاری options دقت کنید، خاصیت observe آن به صورت پیش فرض به body تنظیم شده‌است. به این معنا که تنها body یک response را تبدیل به یک شیء JSON می‌کند:
(method) HttpClient.post(url: string, body: any, options?: {
          headers?: HttpHeaders;
          observe?: "body";
          params?: HttpParams;
          reportProgress?: boolean;
          responseType?: "json";
          withCredentials?: boolean;
}): Observable<Object>
اما گاهی از اوقات نیاز است تا به کل Response دسترسی داشته باشیم. در این حالت باید نوع observe را به response تنظیم کرد:
http
  .get<MyJsonData>('/data.json', {observe: 'response'})
  .subscribe(resp => {
    console.log(resp.headers.get('X-Custom-Header'));
    console.log(resp.body.someField);
  });
به این ترتیب اینبار resp از نوع <HttpResponse<MyJsonData خواهد بود که توسط آن می‌توان به خواص headers و یا body، به صورت جداگانه‌ای دسترسی یافت.


یک نکته‌ی تکمیلی: کدهای سری کار با فرم‌ها در Angular را اگر به HttpClient ارتقاء دهیم، خلاصه‌ی تغییرات آن‌ها به این صورت خواهند بود.
مطالب
پلاگین DataTables کتابخانه jQuery - قسمت سوم
در این قسمت اطلاعات را به صورت ajax از یک فایل متنی می‌خوانیم و آنها را در جدول قرار می‌دهیم. سپس به سفارشی کردن بعضی از قسمت‌های DataTables خواهیم پرداخت.

دریافت اطلاعات به صورت ajax از یک فایل متنی

فرض کنید که اطلاعات در یک فایل txt به صورت اشیاء جاوا اسکریپتی ذخیره شده اند، و این فایل بر روی سرور قرار دارد. می‌خواهیم از این فایل به عنوان منبع داده استفاده کرده و اطلاعات درون آن را به صورت ajax دریافت کرده و در یک جدول html تزریق کنیم. خوشبختانه با استفاده از امکاناتی که این پلاگین تهیه کرده است این کار به سادگی امکان پذیر است.

همان طور که در اینجا بیان شده است ، فرض کنید که جدولی داشته باشیم و بخواهیم اطلاعات راجع به مرورگرهای مختلف را در آن نمایش دهیم. قصد داریم این جدول شامل قسمتهای header و footer و نیز body باشد، بدین صورت:
<table id="browsers-grid">
    <thead>
       <tr>
          <th width="20%">موتور رندرگیری</th>
          <th width="25%">مرورگر</th>
          <th width="25%">پلتفرم (ها)</th>
          <th width="15%">نسخه موتور</th>
          <th width="15%">نمره css</th>
       </tr>
    </thead>

    <tbody>

    </tbody> 

    <tfoot>
       <tr>
          <th>موتور رندرگیری</th>
          <th>مرورگر</th>
           <th>پلتفرم (ها)</th>
          <th>نسخه موتور</th>
          <th>نمره css</th>
       </tr>
    </tfoot>
</table>
برای هر ستون از این جدول عرضی در نظر گرفته شده است. اگر این کار انجام نشود به صورت خودکار به تمام ستونها عرض داده می‌شود.

داده هایی که باید در بدنه جدول قرار بگیرند، در یک فایل متنی روی سرور قرار دارند. محتویات این فایل چیزی شبیه زیر است:
{
   "aaData": [
      {"engine":"Trident", "browser":"Internet Explorer 4.0", "platform":"Win95+", "version":"4", "grade":"X"},
      {"engine":"Trident", "browser":"Internet Explorer 5.0", "platform":"Win95+", "version":"5", "grade":"C"},
      {"engine":"Trident", "browser":"Internet Explorer 5.5", "platform":"Win95+", "version":"5.5", "grade":"A"}
   ]
}
همان طور که مشاهده می‌کنید فرمت ذخیره داده‌ها در این فایل به صورت json یا اشیاء جاوا اسکریپتی است. این اشیاء باید به خصوصیت aaData نسبت داده شوند که در قسمت قبل راجع به آن توضیح دادیم. تعداد این اشیاء 57 تا بود که برای سادگی بیشتر 3 تا از آنها را اینجا ذکر کردیم.

اسکریپتی که داده‌ها را از فایل متنی خوانده و آنها را در جدول قرار می‌دهد هم بدین صورت خواهد بود:
$(document).ready(function () {
        $('#browsers-grid').dataTable({
            "sAjaxSource": "datasource/objects.txt",
            "bProcessing": true,
            "aoColumns": [
                { "mDataProp": "engine" },
                { "mDataProp": "browser" },
                { "mDataProp": "platform" },
                { "mDataProp": "version" },
                { "mDataProp": "grade" }
            ]
    });
});
شرح کد:
sAjaxSource : رشته
نوع داده ای که قبول می‌کند رشته ای و بیان کننده آدرسی است که داده‌ها باید از آنجا دریافت شوند. در اینجا داده‌ها در فایل متنی objects.txt در پوشه datasource قرار دارند.

bProcessing : بولین
نوع داده‌های قابل قبول این خصوصیت true یا false هست و بیان کننده این است که یک پیغام loading تا زمانی که داده‌ها دریافت شوند و در جدول قرار بگیرند نمایش داده شوند یا خیر.


تنظیم کردن گزینه‌های اضافی دیگر

sAjaxDataProp : رشته
همان طور که گفتیم در فایل متنی که حاوی اشیاء json بود ، این اشیاء را به متغیری به اسم aaData منتسب کردیم. این نام را می‌توان تغییر داد مثلا فرض کنید در فایل متنی داده‌ها به متغیری به اسم data منتسب شده اند:
{
   "data": [
      {"engine":"Trident", "browser":"Internet Explorer 4.0", "platform":"Win95+", "version":"4", "grade":"X"},
      {"engine":"Trident", "browser":"Internet Explorer 5.0", "platform":"Win95+", "version":"5", "grade":"C"},
      {"engine":"Trident", "browser":"Internet Explorer 5.5", "platform":"Win95+", "version":"5.5", "grade":"A"}
   ]
}
در این صورت باید خصوصیت sAjaxDataProp را به همان نامی که در فایل متنی مشخص کرده اید مقداردهی کنید، در غیر این صورت داده‌های جدول هیچ گاه بارگذاری نخواهند شد. بدین صورت:
"sAjaxDataProp": "data"
یا اگر داده‌ها را بدین صورت در فایل متنی ذخیره کرده اید:
{ "data": { "inner": [...] } }
آنگاه خصوصیت sAjaxDataProp بدین صورت مقداردهی خواهد شد:
"sAjaxDataProp": "data.inner"

sPaginationType: رشته
نحوه صفحه بندی و حرکت بین صفحات مختلف را بیان می‌کند. اگر با two_buttonمقدار دهی شود (مقدار پیش فرض) حرکت بین صفحات مختلف به وسیله دکمه‌های Next و Previous امکان پذیر خواهد بود. اگر با full_numbersمقدار دهی شود حرکت بین صفحات با دکمه‌های Next و Previous ، و همچنین دکمه‌های First و Last و نیز شماره صفحه فعلی و دو صفحه بعدی و دو صفحه قبلی قابل انجام است.

شکل الف) صفحه بندی به صورت full_numbers

bLengthChange: بولین
بیان می‌کند کاربر بتواند اندازه صفحه را تغییر دهید یا نه. به صورت پیش فرض این گزینه true است. اگر آن به false مقدار دهی شود لیست بازشونده مربوط به اندازه صفحه مخفی خواهد شد.

aLengthMenu  :  آرایه یک بعدی یا دو بعدی
به صورت پیش فرض در لیست باز شونده مربوط به تعداد رکوردهای قابل نمایش در هر صفحه اعداد 10 ، 25 ، 50 ، و 100 قرار دارند.

شکل ب ) لیست بازشونده شامل اندازه‌های صفحه

در صورتی که بخواهیم این گزینه‌ها را تغییر دهیم باید خصوصیت aLengthMenu را مقدار دهی کنیم. اگر مقداری که به این خصوصیت می‌دهیم یک آرایه یک بعدی باشد، مثلا

"aLengthMenu": [25, 50, 100, -1],
نتیجه یک لیست باز شوند است که دارای چهار عنصر است که value و text آنها یکی است. (نکته: چهارمین عنصر از لیست بالا دارای مقدار 1- خواهد بود که با انتخاب این گزینه تمام رکوردها نمایش می‌یابند). اما اگر می‌خواهیم که text و value این عناصر با هم فرق کند از یک آرایه دو بعدی استفاده خواهیم کرد، مثلا:

"aLengthMenu": [[25, 50, 100, -1], ["همه", "صد", "پنجاه", "بیست و پنج"]],

iDisplayLength
: عدد صحیح
تعداد رکوردهای قابل نمایش در هر صفحه هنگامی که داده‌ها در جدول ریخته می‌شوند را معین می‌کند. می‌توانید این را مقداری بدهید که در خصوصیت aLengthMenu ذکر نشده است، مثلا 28 تا.


sDom : رشته
پلاگین DataTables به صورت پیش فرض لیست بازشونده اندازه صفحه و کادر متن مربوط به جستجو را در بالای جدول داده‌ها اضافه می‌کند، و نیز اطلاعات دیگر و همچنین امکانات مربوط به صفحه بندی را به قسمت پایین جدول اضافه می‌کند. شما می‌توانید موقعیت این عناصر را با استفاده از پارامتر sDom تغییر دهید.

نحو (syntax) مقداری که پارامتر sDom قبول می‌کند مقداری عجیب و غریب است، مثلا:

'<"top"iflp<"clear">>rt<"bottom"iflp<"clear">>'

این خط بیان می‌کند که در قسمت بالای جدول یک تگ div با کلاس top قرار بگیرد. در این تگ قسمت اطلاعات (یعنی Showing x to xx from xxx entries) (با حرف i) ، کادر جستجو (با حرف f) ، لیست بازشونده مربوط به اندازه صفحه (با حرف l) ، و نیز قسمت صفحه بندی (با حرف p)قرار خواهند گرفت. در انتهای تگ div با کلاس top، یک تگ div با کلاس clear قرار خواهد گرفت. بعد قسمت مربوط به پیغام loading (با حرف r) و بعد با حرف t جدول حاوی داده‌ها قرار می‌گیرد. در نهایت یک تگ div با کلاس bottom قرار می‌گیرد و با حرفهای i ، و f ، و l و p درون آن قسمتهای اطلاعات ، کادرجستجو، لیست بازشونده اندازه صفحه و نیز قسمت صفحه بندی قرار خواهد گرفت و در نهایت یک تگ div با کلاس clear قرار خواهد گرفت.

حرفهایی که در sDom معنی خاصی می‌دهند :
  • l سر حرف Length Changing برای لیست بازشونده مربوط به اندازه صفحه
  • f سر حرف Filtering input برای قسمت کادر جستجو
  • t سرحرف table برای جدول حاوی داده ها
  • i سر حرف information برای قسمت Showing x to xx from xxx entries
  • p سر حرف pagination برای قسمت صفحه بندی
  • r حرف دوم pRocessing برای قسمت پیغام قبل از بار کردن داده‌های جدول (قسمت loading)
  • H و F که مربوط به theme‌های jQuery UI می‌شوند که بعدا درباره آنها توضیح داده می‌شود.

همچنین بین علامت‌های کوچکتر (>) و بزرگتر (<) یعنی اگر چیزی بیاید در یک تگ div قرار خواهد گرفت. اگر بخواهیم div ی بسازیم و به آن کلاس بدهیم از نحو زیر استفاده خواهیم کرد:

'<"class" and '>'
و اگر بخواهیم یک تگ div با یک id مشخص بسازیم از نحو زیر استفاده خواهیم کرد:
'<"#id" and '>'
در نهایت جدولی مثل جدول زیر تولید خواهد شد:

شکل ج) جدول نهایی تولید شده توسط DataTables

کدهای نهایی این مثال را از DataTables-DoteNetTips-Tutorial-03.zip دریافت کنید.
نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
امکان تهیه Custom Elements در NET 6 Blazor.

در آخرین نسخه Blazor این امکان فراهم شده است که بتوانیم از کامپوننت‌های Blazor درون پروژه‌های React/Vue, Angular, ... استفاده کنیم (+). البته این فیچر هنوز به صورت آزمایشی می‌باشد و ممکن است API آن تغییر کند. 
در ادامه یک مثال از این قابلیت را مشاهده خواهید کرد. 
ایجاد پروژه Blazor
یک دایرکتوری ایجاد کرده و درون آن یک پروژه blazorwasm با نام blazor_wasm ایجاد کنید:
dotnet new blazorwasm blazor_wasm
برای استفاده از این فیچر میبایست پکیج  Microsoft.AspNetCore.Components.CustomElements را نصب کنیم:
dotnet add package Microsoft.AspNetCore.Components.CustomElements --version 0.1.0-alpha.21466.1
در ادامه یک کامپوننت Todo ایجاد خواهیم کرد:
@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" @bind="todo.IsDone" />
            <input @bind="todo.Title" />
        </li>
    }
</ul>

<input placeholder="Something todo" @bind="newTodo" />
<button @onclick="AddTodo">Add todo</button>

@code {

    public class TodoItem
    {
        public string? Title { get; set; }
        public bool IsDone { get; set; }
    }

    private List<TodoItem> todos = new();
    private string? newTodo;

    private async void AddTodo(MouseEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
}
برای تبدیل کامپوننت فوق به یک Custom Element درون فایل Program.cs خط زیر را اضافه میکنیم:
builder.RootComponents.RegisterAsCustomElement<Todo>("todo-element");

استفاده از کامپوننت فوق درون یک پروژه React
npx create-react-app blazor_react && cd blazor_react
برای استفاده از Custom Element موردنظر دو خط زیر را به فایل public/index.html اضافه میکنیم:
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
همچنین لازم است یک پراکسی نیز درون پروژه ایجاد کنیم (درون فایل package.json)؛ با اینکار اسکریپت‌های موردنیاز فوق از سمت سرور دریافت خواهند شد:
"proxy": "BLAZOR_APP_ADDRESS", // for example: http://localhost:5269 
در نهایت درون فایل App.js می‌توانیم از کامپوننت Todo استفاده کنیم:
function App() {
  return (
    <div className="App">
      <todo-element />
    </div>
  );
}

export default App;