مطالب
2# آموزش سیستم مدیریت کد Git

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

تعاریف زیر هر چند برای Git نوشته شده‌اند، اما می‌توانند در بقیه DVCS‌ها نیز کاربرد داشته باشند.

Commit:

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

Pushing:

بعد از انجام عملیات Commit، معمولا برنامه نویسان می‌خواهند کد‌های نوشته شده را با دیگران به اشتراک بگذارند. این کار به وسیله عملیات Pushing صورت می‌گیرد. بنابراین pushing عبارت است از عملی که با استفاده از آن داده‌ها از یک Repository به Repository دیگر جهت به اشتراک گذاری انتقال می‌یابد. معمولا به این مخزن Upstream Repository می‌گویند. Upstream Repository یک مخزن عمومی برای تمامی برنامه نویسانی است که تغییرات فایل‌های خود را در آنجا push می‌کنند.

Pulling:

عملیات Pushing تنها نیمی از آن چیزی است که برنامه نویسان برای حفظ به روز بودن کدهای خود به آن احتیاج دارند. در بسیاری از موارد آن‌ها نیاز دارند تا تغییرات فایل‌ها و آخرین به روز رسانی‌ها را نیز دریافت کنند. این کار در دو مرحله متفاوت انجام می‌شود:

1)بازیابی داده‌ها از مخزن عمومی (fetch)

2)الحاق داده‌های دریافت شده با داده‌های فعلی

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

Branch‌ها (شاخه‌ها):

Branch و یا همان شاخه، به ما این امکان را می‌دهد که بتوانیم برای قسمت‌های مختلف یک پروژه که روند تولید آن‌ها با هم ارتباط مستقیمی ندارند، سوابق فایلی متفاوتی را ایجاد کنیم.

به عنوان مثال تصور کنید که در یک پروژه سه تیم متفاوت وجود دارد

1)تیم توسعه برنامه

2)تیم تست و اشکال یابی

3)واحد گرافیکی

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

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

Merging:

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

البته عملیات Merging  می تواند باعث ایجاد مشکلی به نام Conflict شود که خوشبختانه Git روش هایی را برای مدیریت این مشکل دارد که در مقالات بعد به آن اشاره خواهد شد.

Locking:

با استفاده از این کار می‌توان مانع تغییر یک فایل توسط برنامه نویسان دیگر شد. معولا Locking به 2 صورت است

1)Strict Locking

2) Optimistic Locking

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

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

تعاریف فوق بخشی از مفاهیم اولیه مورد نیاز Git بود. اما ما در ادامه به بررسی object‌های Git و همچنین نحوه ذخیره سازی و مدیریت فایل‌ها در این سیستم مدیریت کد خواهیم پرداخت.

مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت اول
در این سلسله مقالات قصد دارم چندین مطلب راجع به افزایش سرعت نرم افزارهای تحت وب مطرح نمایم. این مطالب هرچند بسیار مختصر می‌باشند ولی در کارایی و سرعت برنامه‌های شما در آینده تاثیر خواهند داشت.
1.کش کردن همیشه آخرین حربه می‌باشد
این مهم است که بخش‌های مختلف سایت شما در سطوح مختلف کش شوند (ASP.NET, Kernel, Server, Proxy Server, Browser ,...) ولی این موضوع باید همیشه آخرین حربه و نکته ای باشد که آن را در مورد سایت خود اعمال می‌کنید.
یعنی همیشه مطمئن شوید ابتدا تمامی نکات مربوط به افزایش کارایی در برنامه خود را رعایت کرده اید، سپس اقدام به کش داده‌ها در سطوح مختلف نمایید. توجه کنید کش کردن داده‌ها و صفحات می‌تواند مشکلات را برای شما به عنوان یک برنامه نویس یا تست کننده برنامه پنهان کند و به شما اطمینان دهد که همه چیز خوب کار می‌کند در حالی که این چنین نیست!
البته ذکر این نکته هم بی فایده نیست که کش کردن همه چیز بعضی مواقع دشمن برنامه شما محسوب می‌شود! هیچ وقت یادم نمی‌رود، در پورتال داخلی یک شرکت که در وقت استراحت به کارکنان اجازه مطالعه روزنامه‌های روز را می‌داد (به صورت آفلاین)، این نکته در بالای صفحه آورده شده بود: «لطفا برای به روز رساندن صفحات روزنامه‌ها از کلید Ctrl+F5 استفاده نمایید». این موضوع یعنی بحث کشینگ در برنامه آن پرتال در سطح فاجعه می‌باشد! حالا فرض کنید این مشکل در فرم ورود و یا مرور اطلاعات یک برنامه به وجود آید...
2.حذف View Engine‌های غیر ضروری
به عنوان یک برنامه نویس ASP.NET MVC، یابد اطلاع داشته باشید که CLR به صورت خودکار View Engine‌های Razor و Web Forms را لود می‌کند. این موضوع به این دلیل است که اطلاعی از نحوه برنامه نویسی شما ندارد. اگر شما فقط از یکی از این دو View Engine استفاده می‌کنید،لطفا دیگری را غیر فعال کنید! فعال بودن هر دوی آنها یعنی اتلاف وقت گرانبهای CPU سرور شما برای رندر کردن تمامی صفحات شما توسط دو انجین! ایتدا view‌های شما با Web Forms Engine رندر شده سپس نتیجه به  Razor Engine منتقل شده و مجدد توسط این انجین رندر می‌شود. این موضوع در سایت‌های با تعداد کاربر بالا یعنی فاجعه!
برای حل این مشکل کافی است خطوط زیر را در فایل Global.asax و در رویداد بخش Application_Start وارد نمایید:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
این دو خط یعنی خداحافظ Web Forms Engine...
قبل از استفاده از این کد، اطمینان حاصل کنید کل برنامه شما توسط Razor تهیه شده است وگرنه بنده هیچ مسئولیتی در رابطه با فریادهای کارفرمای شما متقبل نمی‌شوم!
صد البته برای حذف Razor Engine و استفاده از Web Form Engine می‌توان از کد زیر در همان موقعیت فوق استفاده کرد:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new WebFormViewEngine());
البته همانطور که حتما دوستان مطلع هستند امکان گسترش Engine‌های فوق توسط ارث بری از کلاس BuildManagerViewEngine جهت ایجاد Engine‌های دیگر همیشه محیا است. در این صورت می‌توانید تنها انجین سفارشی مورد نظر خود را لود کرده و از لود دیگر انجین‌ها پرهیز کنید. 
3. استفاده از فایل‌های PDB مایکروسافت برای دیباگ و یا پروفایل کردن DLL‌های دیگران  
دیباگ یا پروفایل کردن برنامه ها، DLL ها، اسمبلی‌ها و منابعی از برنامه که شما آن را خود ننوشته اید (سورس آنها در دسترس شما نمی‌باشد) همیشه یکی از سخت‌ترین مراحل کار می‌باشد. جهت کمک به دیباگر‌ها یا پروفایلرها، نیاز است فایل‌های PDB مرتبط با DLL‌ها را در اختیار آنها قرار دهید تا به بهترین نتیجه دسترسی پیدا کنید. این فایل‌ها محتوی نام توابع، شماره خطوط برنامه و metadata‌های دیگر برنامه اصلی قبل از optimize شدن توسط کامپایلر یا JIT می‌باشد.  خوب حالا اگر نیاز شد این کار را در رابطه با DLL‌ها و کلاس‌های پایه Microsoft.NET انجام دهیم چه کار کنیم؟
خیلی ساده! خود Microsoft سروری جهت این موضوع تدارک دیده که فایل‌های PDB را جهت دیباگ کردن در اختیار تیم‌های برنامه نویسی قرار می‌دهد.کافی است از منوی Tools گزینه Options را انتخاب، سپس به بخش Debugging و به بخش Symbols بروید و گزینه Microsoft Symbol Servers as your source for Symbols را انتخاب کنید. برای اطمینان از اینکه هر مرتبه که برنامه را دیباگ می‌کنید مجبور به دانلود این فایل‌ها نشوید، فراموش نکنید پوشه ای را جهت کش این فایل‌ها ایجاد و آدرس آن را در بخش Cache symbols in this directory همین صفحه وارد نمایید.
این امکان در Visual Studio 2010, 2012 در دسترس می‌باشد.
مطالب
حذف فضاهای خالی در خروجی صفحات ASP.NET MVC
صفحات خروجی وب سایت زمانی که رندر شده و در مرورگر نشان داده می‌شود شامل فواصل اضافی است که تاثیری در نمایش سایت نداشته و صرفا این کاراکترها فضای اضافی اشغال می‌کنند. با حذف این کاراکترهای اضافی می‌توان تا حد زیادی صفحه را کم حجم کرد. برای این کار در ASP.NET Webform کارهایی (^ ) انجام شده است.
روال کار به این صورت بوده که قبل از رندر شدن صفحه در سمت سرور خروجی نهایی بررسی شده و با استفاده از عبارات با قاعده الگوهای مورد نظر لیست شده و سپس حذف می‌شوند و در نهایت خروجی مورد نظر حاصل خواهد شد. برای راحتی کار و عدم نوشتن این روال در تمامی صفحات می‌تواند در مستر پیج این عمل را انجام داد. مثلا:
private static readonly Regex RegexBetweenTags = new Regex(@">\s+<", RegexOptions.Compiled);
        private static readonly Regex RegexLineBreaks = new Regex(@"\r\s+", RegexOptions.Compiled);

        protected override void Render(HtmlTextWriter writer)
        {
            using (var htmlwriter = new HtmlTextWriter(new System.IO.StringWriter()))
            {
                base.Render(htmlwriter);
                var html = htmlwriter.InnerWriter.ToString();

                html = RegexBetweenTags.Replace(html, "> <");
                html = RegexLineBreaks.Replace(html, string.Empty);
                html = html.Replace("//<![CDATA[", "").Replace("//]]>", "");
                html = html.Replace("// <![CDATA[", "").Replace("// ]]>", "");

                writer.Write(html.Trim());
            }
        }
در هر صفحه رویدادی به نام Render وجود دارد که خروجی نهایی را می‌توان در آن تغییر داد. همانگونه که مشاهده می‌شود عملیات یافتن و حذف فضاهای خالی در این متد انجام می‌شود.
این عمل در ASP.NET Webform به آسانی انجام شده و باعث حذف فضاهای خالی در خروجی صفحه می‌شود.
برای انجام این عمل در ASP.NET MVC روال کار به این صورت نیست و نمی‌توان مانند ASP.NET Webform عمل کرد.
چون در MVC از ViewPage استفاده می‌شود و ما مستقیما به خروجی آن دسترسی نداریم یک روش این است که می‌توانیم یک کلاس برای ViewPage تعریف کرده و رویداد Write آن را تحریف کرده و مانند مثال بالا فضای خالی را در خروجی حذف کرد. البته برای استفاده باید کلاس ایجاد شده را به عنوان فایل پایه جهت ایجاد صفحات در MVC فایل web.config معرفی کنیم. این روش در اینجا به وضوح شرح داده شده است.
اما هدف ما پیاده سازی با استفاده از اکشن فیلتر هاست. برای پیاده سازی ایتدا یک اکشن فیلتر به نام CompressAttribute تعریف می‌کنیم مانند زیر:
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;

namespace PWS.Common.ActionFilters
{
    public class CompressAttribute : ActionFilterAttribute
    {
         #region Methods (2) 

        // Public Methods (1) 

        /// <summary>
        /// Called by the ASP.NET MVC framework before the action method executes.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var response = filterContext.HttpContext.Response;
            if (IsGZipSupported(filterContext.HttpContext.Request))
            {
                String acceptEncoding = filterContext.HttpContext.Request.Headers["Accept-Encoding"];
                if (acceptEncoding.Contains("gzip"))
                {
                    response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "gzip");
                }
                else
                {
                    response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "deflate");
                }
            }
            // Allow proxy servers to cache encoded and unencoded versions separately
            response.AppendHeader("Vary", "Content-Encoding");
           //حذف فضاهای خالی
response.Filter = new WhitespaceFilter(response.Filter); } // Private Methods (1)  /// <summary> /// Determines whether [is G zip supported] [the specified request]. /// </summary> /// <param name="request">The request.</param> /// <returns></returns> private Boolean IsGZipSupported(HttpRequestBase request) { String acceptEncoding = request.Headers["Accept-Encoding"]; if (acceptEncoding == null) return false; return !String.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"); } #endregion Methods  } /// <summary> /// Whitespace Filter /// </summary> public class WhitespaceFilter : Stream { #region Fields (3)  private readonly Stream _filter; /// <summary> /// /// </summary> private static readonly Regex RegexAll = new Regex(@"\s+|\t\s+|\n\s+|\r\s+", RegexOptions.Compiled); /// <summary> /// /// </summary> private static readonly Regex RegexTags = new Regex(@">\s+<", RegexOptions.Compiled); #endregion Fields  #region Constructors (1)  /// <summary> /// Initializes a new instance of the <see cref="WhitespaceFilter" /> class. /// </summary> /// <param name="filter">The filter.</param> public WhitespaceFilter(Stream filter) { _filter = filter; } #endregion Constructors  #region Properties (5)  //methods that need to be overridden from stream /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports reading. /// </summary> /// <returns>true if the stream supports reading; otherwise, false.</returns> public override bool CanRead { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports seeking. /// </summary> /// <returns>true if the stream supports seeking; otherwise, false.</returns> public override bool CanSeek { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports writing. /// </summary> /// <returns>true if the stream supports writing; otherwise, false.</returns> public override bool CanWrite { get { return true; } } /// <summary> /// When overridden in a derived class, gets the length in bytes of the stream. /// </summary> /// <returns>A long value representing the length of the stream in bytes.</returns> public override long Length { get { return 0; } } /// <summary> /// When overridden in a derived class, gets or sets the position within the current stream. /// </summary> /// <returns>The current position within the stream.</returns> public override long Position { get; set; } #endregion Properties  #region Methods (6)  // Public Methods (6)  /// <summary> /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed. /// </summary> public override void Close() { _filter.Close(); } /// <summary> /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// </summary> public override void Flush() { _filter.Flush(); } /// <summary> /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// </summary> /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns> /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. /// </returns> public override int Read(byte[] buffer, int offset, int count) { return _filter.Read(buffer, offset, count); } /// <summary> /// When overridden in a derived class, sets the position within the current stream. /// </summary> /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param> /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param> /// <returns> /// The new position within the current stream. /// </returns> public override long Seek(long offset, SeekOrigin origin) { return _filter.Seek(offset, origin); } /// <summary> /// When overridden in a derived class, sets the length of the current stream. /// </summary> /// <param name="value">The desired length of the current stream in bytes.</param> public override void SetLength(long value) { _filter.SetLength(value); } /// <summary> /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. /// </summary> /// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param> /// <param name="count">The number of bytes to be written to the current stream.</param> public override void Write(byte[] buffer, int offset, int count) { string html = Encoding.Default.GetString(buffer); //remove whitespace html = RegexTags.Replace(html, "> <"); html = RegexAll.Replace(html, " "); byte[] outdata = Encoding.Default.GetBytes(html); //write bytes to stream _filter.Write(outdata, 0, outdata.GetLength(0)); } #endregion Methods  } }
در این کلاس فشرده سازی (gzip و deflate نیز اعمال شده است) در متد OnActionExecuting ابتدا در خط 24 بررسی می‌شود که آیا درخواست رسیده gzip را پشتیبانی می‌کند یا خیر. در صورت پشتیبانی خروجی صفحه را با استفاده از gzip یا deflate فشرده سازی می‌کند. تا اینجای کار ممکن است مورد نیاز ما نباشد. اصل کار ما (حذف کردن فضاهای خالی) در خط 42 اعمال شده است. در واقع برای حذف فضاهای خالی باید یک کلاس که از Stream ارث بری دارد تعریف شده و خروجی کلاس مورد نظر به فیلتر درخواست ما اعمال شود.
در کلاس WhitespaceFilter با تحریف متد Write الگوهای فضای خالی موجود در درخواست یافت شده و آنها را حذف می‌کنیم. در نهایت خروجی این کلاس که از نوع استریم است به ویژگی فیلتر صفحه اعمال می‌شود.

برای معرفی فیلتر تعریف شده می‌توان در فایل Global.asax در رویداد Application_Start به صورت زیر فیلتر مورد نظر را به فیلترهای MVC اعمال کرد.
GlobalFilters.Filters.Add(new CompressAttribute());
برای آشنایی بیشتر فیلترها در ASP.NET MVC را مطالعه نمایید.
پ.ن: جهت سهولت، در این کلاس ها، صفحات فشرده سازی و همزمان فضاهای خالی آنها حذف شده است.
مطالب
بررسی تغییرات Blazor 8x - قسمت چهاردهم - امکان استفاده از کامپوننت‌های Blazor در برنامه‌های ASP.NET Core 8x
ASP.NET Core 8x به همراه یک IResult جدید به‌نام RazorComponentResult است که توسط آن می‌توان در Endpoint‌های Minimal-API و همچنین اکشن متدهای MVC، از کامپوننت‌های Blazor، خروجی گرفت. این خروجی نه فقط static یا به عبارتی SSR، بلکه حتی می‌تواند تعاملی هم باشد. در این مطلب، جزئیات فعالسازی و استفاده از این IResult جدید را در یک برنامه‌ی Minimal-API بررسی می‌کنیم.


ایجاد یک برنامه‌ی Minimal-API جدید در دات نت 8

پروژه‌ای را که در اینجا پیگیری می‌کنیم، بر اساس قالب استاندارد تولید شده‌ی توسط دستور dotnet new webapi تکمیل می‌شود.


ایجاد یک صفحه‌ی Blazor 8x به همراه مسیریابی و دریافت پارامتر

در ادامه قصد داریم که یک کامپوننت جدید را به نام SsrTest.razor در پوشه‌ی جدید Components\Tests ایجاد کرده و برای آن مسیریابی از نوع page@ هم تعریف کنیم. یعنی نه‌فقط قصد داریم آن‌را توسط RazorComponentResult رندر کنیم، بلکه می‌خواهیم اگر آدرس آن‌را در مرورگر هم وارد کردیم، قابل دسترسی باشد.
به همین جهت یک پوشه‌ی جدید را به نام Components در ریشه‌ی پروژه‌ی Web API جاری ایجاد می‌کنیم، با این محتوا:
برای ایده گرفتن از محتوای مورد نیاز، به «معرفی قالب‌های جدید شروع پروژه‌های Blazor در دات نت 8» قسمت دوم این سری مراجعه کرده و برای مثال قالب ساده‌ترین حالت ممکن را توسط دستور زیر تولید می‌کنیم (در یک پروژه‌ی مجزا، خارج از پروژه‌ی جاری):
dotnet new blazor --interactivity None
پس از اینکار، محتویات پوشه‌ی Components آن‌را مستقیما داخل پوشه‌ی پروژه‌ی Minimal-API جاری کپی می‌کنیم. یعنی در نهایت در این پروژه‌ی جدید Web API، به فایل‌های زیر می‌رسیم:
- فایل Imports.razor_ ساده شده برای سهولت کار با فضاهای نام در کامپوننت‌های Blazor (فضاهای نامی را که در آن وجود ندارند و مرتبط با پروژه‌ی دوم هستند، حذف می‌کنیم).
- فایل App.razor، برای تشکیل نقطه‌ی آغازین برنامه‌ی Blazor.
- فایل Routes.razor برای معرفی مسیریابی صفحات Blazor تعریف شده.
- پوشه‌ی Layout برای معرفی فایل MainLayout.razor که در Routes.razor استفاده شده‌است.

و ... یک فایل آزمایشی جدید به نام Components\Tests\SsrTest.razor با محتوای زیر:
@page "/ssr-page/{Data:int}"

<PageTitle>An SSR component</PageTitle>

<h1>An SSR component rendered by a Minimal-API!</h1>

<div>
    Data: @Data
</div>

@code {

    [Parameter]
    public int Data { get; set; }

}
این فایل، می‌تواند پارامتر Data را از طریق فراخوانی مستقیم آدرس فرضی http://localhost:5227/ssr-page/2 دریافت کند و یا ... از طریق خروجی جدید RazorComponentResult که توسط یک Endpoint سفارشی ارائه می‌شود:




تغییرات مورد نیاز در فایل Program.cs برنامه‌ی Web-API برای فعالسازی رندر سمت سرور Blazor

در ادامه کل تغییرات مورد نیاز جهت اجرای این برنامه را مشاهده می‌کنید:
var builder = WebApplication.CreateBuilder(args);

// ...

builder.Services.AddRazorComponents();

// ...

// http://localhost:5227/ssr-component?data=2
// or it can be called directly http://localhost:5227/ssr-page/2
app.MapGet("/ssr-component",
           (int data = 1) =>
           {
               var parameters = new Dictionary<string, object?>
                                {
                                    { nameof(SsrTest.Data), data },
                                };
               return new RazorComponentResult<SsrTest>(parameters);
           });

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>();
app.Run();

// ...
توضیحات:
- همین اندازه تغییر در جهت فعالسازی رندر سمت سرور کامپوننت‌های Blazor در یک برنامه‌ی ASP.NET Core کفایت می‌کند. یعنی اضافه شدن:
AddRazorComponents ،UseAntiforgery و MapRazorComponents
- در اینجا نحوه‌ی ارسال پارامترها را به یک RazorComponentResult نیز مشاهده می‌کنید.
- در حالت فراخوانی از طریق مسیر endpoint (یعنی فراخوانی مسیر http://localhost:5227/ssr-component در مثال فوق)، خود کامپوننت فراخوانی شده، بدون layout تعریف شده‌ی در فایل App.razor، رندر می‌شود. علت اینجا است که layout برنامه به همراه کامپوننت Router و RouteView آن فعال می‌شود که این دو هم مختص به صفحات دارای مسیریابی Blazor هستند و برای رندر کامپوننت‌های خالص آن بکار گرفته نمی‌شوند. خروجی RazorComponentResult تنها یک static SSR خالص است؛ مگر اینکه فایل blazor.web.js را نیز بارگذاری کند.

یک نکته: اگر در حالت رندر توسط RazorComponentResult، علاقمند به استفاده‌ی از layout هستید، می‌توان از کامپوننت LayoutView داخل یک کامپوننت فرضی به صورت زیر استفاده کرد؛ اما این مورد هم شامل اطلاعات فایل App.razor نمی‌شود:
<LayoutView Layout="@typeof(MainLayout)">
    <PageTitle>Home</PageTitle>

    <h2>Welcome to your new app.</h2>
</LayoutView>


سؤال: آیا در این حالت کامپوننت‌های تعاملی هم کار می‌کنند؟

پاسخ: بله. فقط برای ایده گرفتن، یک نمونه پروژه‌ی تعاملی Blazor 8x را در ابتدا ایجاد کنید و قسمت‌های اضافی AddRazorComponents و MapRazorComponents آن‌را در اینجا کپی کنید؛ یعنی برای مثال جهت فعالسازی کامپوننت‌های تعاملی Blazor Server، به این دو تغییر زیر نیاز است:
// ...

builder.Services.AddRazorComponents()
       .AddInteractiveServerComponents();

// ...

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

// ...
همچنین باید دقت داشت که امکانات تعاملی، به دلیل وجود و دسترسی به یک سطر ذیل که در فایل Components\App.razor واقع شده، اجرایی می‌شوند:
<script src="_framework/blazor.web.js"></script>
و همانطور که عنوان شد، اگر از روش new RazorComponentResult استفاده می‌شود، باید این سطر را به صورت دستی اضافه‌کرد؛ چون به همراه رندر layout تعریف شده‌ی در فایل App.razor نیست. برای مثال فرض کنید کامپوننت معروف Counter را به صورت زیر داریم که حالت رندر آن به InteractiveServer تنظیم شده‌است:
@rendermode InteractiveServer

<h1>Counter</h1>

<p role="status">Current count: @_currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int _currentCount;

    private void IncrementCount()
    {
        _currentCount++;
    }

}
در این حالت پس از تعریف endpoint زیر، خروجی آن فقط یک صفحه‌ی استاتیک SSR خواهد بود و دکمه‌ی Click me آن کار نمی‌کند:
// http://localhost:5227/server-interactive-component
app.MapGet("/server-interactive-component", () => new RazorComponentResult<Counter>());
علت اینجا است که اگر به سورس HTML رندر شده مراجعه کنیم، خبری از درج اسکریپت blazor.web.js در انتهای آن نیست. به همین جهت برای مثال فایل جدید CounterInteractive.razor را به صورت زیر اضافه می‌‌کنیم که ساختار آن شبیه به فایل App.razor است:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive server component</title>
    <base href="/"/>
</head>
<body>
   <h1>Interactive server component</h1>

   <Counter/>

  <script src="_framework/blazor.web.js"></script>
</body>
</html>
و هدف اصلی از آن، تشکیل یک قالب و درج اسکریپت blazor.web.js در انتهای آن است.
سپس تعریف endpoint متناظر را به صورت زیر تغییر می‌دهیم:
// http://localhost:5227/server-interactive-component
app.MapGet("/server-interactive-component", () => new RazorComponentResult<CounterInteractive>());
اینبار به علت بارگذاری فایل blazor.web.js، امکانات تعاملی کامپوننت Counter فعال شده و قابل استفاده می‌شوند.


سؤال: آیا می‌توان این خروجی static SSR کامپوننت‌های بلیزر را در سرویس‌های یک برنامه ASP.NET Core هم دریافت کرد؟

منظور این است که آیا می‌توان از یک کامپوننت Blazor، به همراه تمام پیشرفت‌های Razor در آن که در Viewهای MVC قابل دسترسی نیستند، به‌شکل یک رشته‌ی خالص، خروجی گرفت و برای مثال از آن به‌عنوان قالب پویای محتوای ایمیل‌ها استفاده کرد؟
پاسخ: بله! زیر ساخت RazorComponentResult که از سرویس HtmlRenderer استفاده می‌کند، بدون نیاز به برپایی یک endpoint هم قابل دسترسی است:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace WebApi8x.Services;

public class BlazorStaticRendererService
{
    private readonly HtmlRenderer _htmlRenderer;

    public BlazorStaticRendererService(HtmlRenderer htmlRenderer) => _htmlRenderer = htmlRenderer;

    public Task<string> StaticRenderComponentAsync<T>() where T : IComponent
        => RenderComponentAsync<T>(ParameterView.Empty);

    public Task<string> StaticRenderComponentAsync<T>(Dictionary<string, object?> dictionary) where T : IComponent
        => RenderComponentAsync<T>(ParameterView.FromDictionary(dictionary));

    private Task<string> RenderComponentAsync<T>(ParameterView parameters) where T : IComponent =>
        _htmlRenderer.Dispatcher.InvokeAsync(async () =>
                                             {
                                                 var output = await _htmlRenderer.RenderComponentAsync<T>(parameters);
                                                 return output.ToHtmlString();
                                             });
}
برای کار با آن، ابتدا باید سرویس فوق را به صورت زیر ثبت و معرفی کرد:
builder.Services.AddScoped<HtmlRenderer>();
builder.Services.AddScoped<BlazorStaticRendererService>();
و سپس یک نمونه مثال فرضی نحوه‌ی تزریق و فراخوانی سرویس BlazorStaticRendererService به صورت زیر است که در آن روش ارسال پارامترها هم بررسی شده‌است:
app.MapGet("/static-renderer-service-test",
           async (BlazorStaticRendererService renderer, int data = 1) =>
           {
               var parameters = new Dictionary<string, object?>
                                {
                                    { nameof(SsrTest.Data), data },
                                };
               var html = await renderer.StaticRenderComponentAsync<SsrTest>(parameters);
               return Results.Content(html, "text/html");
           });

کدهای کامل این مطلب را می‌توانید از اینجا دریافت کنید: WebApi8x.zip
نظرات مطالب
SignalR
من خودم تخصصی در زمینه node.js ندارم. البته ازش خوشم هم نمیاد :)
اینجوری که خودشون میگن این یه پلتفرمه که از Chrome's JavaScript runtime استفاده میکنه و شونصدتا ویژگی دیگه هم داره! برای استفاده ازش باید برنامشو رو که بهش node میگن دریافت و نصب کنین (حدود 3 مگابایت نسخه 0.6.19). بعد کد سمت سرور کاملا با استفاده از زبان جاوااسکریپت نوشته میشه (عیب) بنابراین کاملا cross platform هستش (مزیت). یعنی با استفاده از جاوااسکریپت میتونین مثلا یه وب سرور بسیار سبک (مزیت) ایجاد کنین که به یکسری درخواستا پاسخ میده. حالا یکسری اومدن برای این پلتفرم ماژولهایی نوشتن مثل socket.io و nowJS که برقراری ارتباطی راحت و آسان با سرور و تبادل داده‌ها رو به عهده میگیره (مزیت).
در مقابل SignalR یه کتابخونه غیرهمزمان (Async) هستش که برا دات نت نوشته شده (مزیت)، مخصوص وب زمان واقعی چندکاربره و شونصدتا ویژگی دیگه هم نداره (مزیت) :). بنابراین سمت سرور از مزایای بیشماری برخورداره، مثل یک IDE قدرتمند (VS) (مزیت2x) و یک کتابخونه کامل (NetFx.) (مزیت2x)
درضمن یه فریمورک دیگه هم برای ASP.NET وجود داره: PokeIn که نسخه تجاری (پولی) هم داره و از WebSocket استفاده میکنه. جایی خوندم که کاراییش خوبه در حد 10 تا 50 هزار کلاینت همزمان(^).
برای SignalR یه اپلیکیشن توسط خود نویسندگانش نوشته شده (SignalR-Crank) که برای آزمایش بار روی سرور استفاده میشه. طبق گفته خودشون تا 100k (صد هزار!) کلاینت رو تونستن بدون هیچ مشکلی راه بندازن (البته با مصرف 5 گیگ رم). نمیدونم این تست چی بوده ولی این عدد خیلی زیاده.
در مورد بحث کارآیی خودم دارم یه تستایی انجام میدم. نمیدونم بتونم آزمایش مشابهی رو بقیه فریمورکها انجام بدم یا نه. اگه نتیجه‌ای حاصل شد اینجا میزارم.
با تشکر
مطالب
مدیریت اسپم‌ها در SignalR
سناریویی را در نظر بگیرید که در آن یک برنامه‌ی چت را با استفاده از SignalR نوشته‌اید و قصد دارید از آن در یک سایت شلوغ استفاده کنید. در حالت عادی برنامه به خوبی کار می‌کند؛ تا زمانیکه کسی شروع به ارسال Spam در سیستم چت شما نکرده باشد. برای کنترل ارسال spam، اولین راهی که به ذهن می‌رسد این است که سمت کلاینت کلید ارسال پیغام را چند ثانیه غیر فعال کنیم تا کاربران نتوانند در عرض به طور مثال 3 ثانیه، تعداد زیادی پیام را ارسال کنند. برای کاربران معمولی وب سایت ما این راه حل جواب می‌دهد و مشکلی نیست. ولی اگر کاربر وب‌سایت آشنایی مختصری با JavaScript و نحوه‌ی اتصال به signalR داشته باشد چطور؟ خیلی ساده می‌تواند در کنسول browser خود یک لوپ نوشته و شروع به ارسال spam به برنامه‌ی چت ما کند. اینجاست که مجبوریم به علاوه‌ی کنترل سمت کلاینت، سمت سرور هم زمان ارسال پیام‌ها را کنترل کنیم که در ادامه به یکی از روش‌های انجام این کار می‌پردازیم.

کد اولیه‌ی هاب چت برنامه:
<HubName("chat")>
Public Class ChatHub
    Inherits Hub
    Public Sub sendMessage(msg As String) 
         Clients.All.getMessage(msg) 'Call getMessage function client side
    End Sub
End Class
برای شروع کار ابتدا نیاز به کلاسی داریم که اطلاعات فعالیت‌های کانکشن‌ها را برای ما نگه‌داری کند:
Public Class ActivityInfo
    Sub New(connectionId As String)
        Me.ConnectionID = connectionId
        Me.Time = Now
    End Sub
    Public Property ConnectionID As String
    Public Property Time As DateTime
End Class
سپس برای اینکه بتوانیم تمامی درخواست‌های ارسالی از سمت کلاینت‌ها را مدیریت کنیم، به یک کلاس از نوع HubPipelineModule احتیاج داریم که ما در اینجا با استفاده از کلاس SpamDetectionPiplelineModule که از HubPipelineModule مشتق شده، این کار را انجام می‌دهیم.
Public Class SpamDetectionPiplelineModule
    Inherits HubPipelineModule
    Public Property SpamDetection As New HashSet(Of ActivityInfo)
    Private _SpamDetectionLock As New Object
    Public Function IsSpam(ConnectionId As String) As Boolean
        SyncLock _SpamDetectionLock
            'Remove all old info before 3 seconds ago
            SpamDetection.RemoveWhere(Function(q) q.Time < Now.AddSeconds(-3))

            SpamDetection.Add(New ActivityInfo(ConnectionId))

            'Check activities from 3 seconds ago
            If SpamDetection.Where(Function(q) q.ConnectionID = ConnectionId).Count > 2 Then
                Return True
            Else
                Return False
            End If
        End SyncLock
    End Function
    Protected Overrides Function OnBeforeIncoming(context As IHubIncomingInvokerContext) As Boolean
        If IsSpam(context.Hub.Context.ConnectionId) Then
            Return False
        End If
        Return MyBase.OnBeforeIncoming(context)
    End Function
End Class
در کدهای بالا ابتدا یک HashSet را جهت نگه داشتن فعالیت کاربران ایجاد کردیم که بعد از هر درخواستی که به سمت سرور ارسال می‌شود، به‌روز خواهد شد. متد OnBeforeIncoming قبل از اینکه درخواست کاربر به Hub برنامه برسد، اجرا می‌شود که میتوانیم با استفاده از آن، فعالیت‌های کانکشن کاربر را در طی چند ثانیه‌ی اخیر کنترل کنیم که برای مثال چه تعدادی درخواست را در طی 3 ثانیه‌ی قبل ارسال کرده است. اگر تعداد درخواست‌های ارسالی در طی 3 ثانیه‌ی قبل، بیشتر از تعداد مورد نظر ما بود، با بازگشت دادن False، از اجرای درخواست آن جلوگیری می‌کنیم. (در اینجا ما کاربران را به ارسال 2 پیام در طی هر 3 ثانیه، محدود کرده‌ایم).
حال برای استفاده‌ی از PipelineModule سفارشی خودمان، باید آن را قبل از مپ کردن SignaR، در StartUp برنامه اضافه کنیم:
Public Class Startup
    Public Sub Configuration(app As IAppBuilder)
        GlobalHost.HubPipeline.AddModule(New SpamDetectionPiplelineModule)
        app.MapSignalR
    End Sub
End Class
مطالب
استفاده از 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 کار می‌کنند، این روال هم برای آنها هم صادق می‌باشد.

برای دریافت پروژه اینجا کلیک نمائید.
مطالب
استفاده از bower در visual studio
اگر از آن دسته افرادی هستید که با پکیج‌های مختلف و پروژه‌های مختلف تحت کلاینت سر و کار دارید و همچنین اطلاعات چندانی نسبت به NodeJs ندارید (مثل خود من)، حتما به پروژه‌هایی در Github برخوردید که نیازمند نصب وابستگی‌ها از خط فرمان bower و یا npm هستند.
بعد از مطالعه‌ی مطلب آشنایی با bower این نیاز ایجاد شد تا در پروژه‌هایی که قرار است درون Visual Studio اجرا شوند، وابستگی‌های bower چگونه می‌توانند مدیریت شوند.
خوشبختانه Microsoft این امکان را ایجاد کرده تا شما بتوانید پروژه‌هایی را که وابستگی‌هایشان درون bower تعریف شده را نیز درون Visual Studio حل و فصل کنید. در ادامه تمامی این مراحل، قدم به قدم اضافه تشریح شده است.
قابل ذکر است که هر سه package manager معروف npm، bower و Nuget در ویژوال استدیو 2015 به صورت توکار موجود هستند. 
جزیات بیشتر در مستندات مایکروسافت


معرفی پکیج Bower

همانطور که در مقاله آشنایی با bower نیز اشاره شد، bower یک package manager برای تکنولوژی‌ها و کتابخانه‌های کلاینت است. این package manager بر روی Git اجرا می‌شود. همانطور که می‌دانید تمامی پکیج‌ها نیز از Git دریافت می‌شود.
اما حال اینکه چگونه می‌توان از این package manager در سمت Visual studio بدون نصب NodeJs و Git استفاده کرد، با پکیج توسعه داده شده Bower مایکروسافت رفع شده‌است.
جزئیات این پکیج را میتوانید در NuGet  مطالعه کنید.


شروع کار با Bower

برای آغاز، یک پروژه‌ی web Application ایجاد می‌کنیم. من Empty را انتخاب و ریسورس‌های MVC را هم اضافه کردم.
حال در بخش Package Manager Console دستور زیر را اجرا کنید:
Install-Package Bower
پس از نصب وابستگی‌ها و خود bower خروجی package manager console به صورت زیر خواهد بود:

مشاهده می‌کنید که فولدر .bin به پروژه‌ی شما اضافه شده است.

 حال درون صفحه‌ی cmd (توجه کنید cmd، نه package manager console) به آدرس پروژه (نه solution) با دستور زیر منتقل شوید:

cd <Project Location>

که به جای project location آدرس فایل پروژه را قرار می‌دهیم. شکل زیر نمایانگر این مسیر است:

با اجرای دستور زیر bower.json را به پروژه اضافه می‌کنیم:

bower init

مشاهده می‌کنید که پس از دستور bower init مواردی که قرار است درون bower قرار گیرد، مقدار دهی می‌شوند. من مقادیر را به صورت زیر (حالت‌های پیش فرض) تکمیل کردم:

حال باید تا اینجای کار یک فایل bower.json برای شما در روت پروژه ساخته شده باشد. حال بیایید اولین اسکریپت رفرنس را به پروژه اضافه نماییم. من قصد دارم تا با دستور زیر JQuery را به پروژه اضافه کنم:

bower install jquery

پکیج JQuery به صورت زیر دانلود می‌شود و در پوشه‌ی bower_component در روت پروژه قرار می‌گیرد.

به همین صورت شما می‌توانید تمامی نیازمندی‌های پروژه را از Git با استفاده از bower package manager دریافت کنید.

نظرات مطالب
تعریف قالب‌های جداول سفارشی و کار با منابع داده‌ای از نوع Anonymous در PdfReport
این کتابخانه وابسته به MVC یا WinForms و امثال آن نیست. بر اساس دیتاسورس شما کار می‌کند و سایر تنظیماتی که با کدنویسی مشخص می‌کنید.
یکبار یک قالب کلی برای آن تهیه کنید. سپس از روش تولید پویای ستون‌ها استفاده کنید:
الف) تولید پویای ستون‌ها در حالت استفاده از SQL خام
ب) تولید پویای ستون‌ها در حالت استفاده از ORMها
مطالب
راه‌اندازی Http Interceptor در Angular
ماژول Http در Angular، برای برقراری ارتباط بین کلاینت و سمت سرور، مورد استفاده قرار می‌گیرد. معمولا هنگام ساخت درخواست‌های Http، یکسری کدهای تکراری برای تنظیم هدر (برای اعتبارسنجی و همچنین تنظیمات دیگر) نوشته می‌شوند که در هر درخواست یکسان هستند. همچنین بعد از آمدن جواب (Response) از سمت سرور نیز یکسری کدهای تکراری جهت برسی کد response و یا تغییر فرمت اطلاعات رسیده، به ساختار مورد توافق نوشته خواهند شد.

برای مثال در صورتیکه در برنامه خود از اعتبار سنجی مبتنی بر توکن (Token Base Authentication) استفاده می‌کنید، قبل از ارسال هر درخواست (request)، کدهایی مشابه کد زیر باید نوشته شوند:
let headers = new Headers();
let token = localStorage.getItem('token');
headers.append('Authorization', 'bearer ' + token);
this.http.get('/api/controller/action', { headers: headers })

همچنین فرض کنید بعد از رسیدن جواب هر درخواست، می‌خواهید response code را چک کنید و خطاهای احتمالی را مدیریت کنید. مثلا درصورت دریافت کد 401، کاربر را به صفحه «ورود» و با دریافت کد 404 آنرا را به صفحه «یافت نشد» هدایت کنید و یا با دریافت کد 403 یا 500 پیغام مناسبی را نمایش دهید. بدیهی است در این صورت بعد از هر آمدن پاسخ از سمت سرور (response)، این کدها بایستی تکرار شوند.

جهت پرهیز از این کدهای تکراری، می‌توان برای ماژول Http، یک interceptor واحد درنظر گرفت که تمامی کدهای تکراری را تنها یکبار داخل آن پیاده سازی کرد. مزیت این روش، مدیریت راحت کد، کاهش پیچیدگی‌ها و همچنین حذف کدهای تکراری و یکسان سازی آنها است.
هرچند در Angular دیگر به مانند Angular 1.x مفهوم intercept بر روی Http را به صورت توکار نداریم، ولی به دلایل زیر ما نیاز به پیاده سازی interceptor برای ماژول Http را داریم:
- تنظیم هدرهای سفارشی و اصلاح آدرس، قبل از ارسال درخواست به سمت سرور
- تنظیم token در هدر درخواست، جهت اعتبار سنجی
- مدیریت سراسری خطاهای Http
- انجام هرگونه عملیات crosscutting

حالا که Angular مفهموم intercept را برای ماژول Http خود به صورت توکار درنظر نگرفته است، راه‌حل چیست؟ بهترین راه‌حل برای پیاده سازی موارد مطرح شده در بالا، ارث بری و یا گسترش (extend) مستقیم ماژول Http است:
import { Injectable } from "@angular/core";
import { ConnectionBackend, RequestOptions, Request, RequestOptionsArgs, Response, Http, Headers } from "@angular/http";
import { Observable } from "rxjs/Rx";
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

@Injectable()
export class InterceptedHttp extends Http {
    constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
        super(backend, defaultOptions);
    }

    request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
        // اصلاح url
        if (typeof (url) === 'string')
            url = this.updateUrl((url as string));
        else
            url.url = this.updateUrl((url as Request).url);

        return super.request(url, this.getRequestOptionArgs(options)).catch((err: Response) => {
            // Exception handling
            switch (err.status) {
                case 400:
                    console.log('interceptor: 400');
                    console.log(err);
                    break;
                case 404:
                    console.log('interceptor: 404');
                    console.log(err);
                    break;
                case 500:
                    console.log('interceptor: 500');
                    console.log(err);
                    break;
                case 401:
                    console.log('interceptor: 401');
                    console.log(err);
                    break;
                case 403:
                    console.log('interceptor: 403');
                    console.log(err);
                    break;
                default:
                    console.log('interceptor: ' + err.status);
                    console.log(err);
            }
            return Observable.throw(err);

        });
    }

    private updateUrl(req: string) {
        return `http://localhost:61366/api/${req}`
    }

    private getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs {
        if (options == null) {
            options = new RequestOptions();
        }
        if (options.headers == null) {
            options.headers = new Headers();
        }
        // هدر درخواست تنظیم
        let token = localStorage.getItem('token');
        options.headers.append('Authorization', 'bearer ' + token);


        return options;
    }
}
تمامی افعال Http، از جمله get ،post ،put ،delete ،patch ،head و options در نهایت از متد request موجود در ماژول http برای ارسال درخواست خود استفاده می‌کنند. به همین جهت تمامی عملیات crosscutting را در این متد پیاده سازی کرده‌ایم. به علاوه قبل از ارسال درخواست، توسط متد updateUrl آدرس url خود را اصلاح کرده‌ایم. همچنین توسط متد getRequestOptionArgs هدر درخواست را جهت اعتبار سنجی مقداردهی کرده‌ایم. در اینجا بعد از ارسال درخواست و آمدن response از سمت سرور، توسط catch خطاهای احتمالی را برسی کرده‌ایم.

نکته: به عنوان مثال، در صورتیکه قصد دارید، برای درخواست‌هایی از جنس get، هدر متفاوتی نسبت به دیگر درخواست‌ها داشته باشید، آنگاه پیاده سازی عملیات اصلاح هدر در متد request جواب کار را نخواهد داد. برای حل این موضوع می‌توانید به جای اصلاح header در متد request، تمامی متدهای get ،post، put ،delete ،patch ،head و options را باز نویسی کرده و در هرکدام از این متدها اینکار را انجام دهید.

حالا با تغییر قسمت providers در ماژول اصلی برنامه به شکل زیر، Angular را مجبور می‌کنیم بجای استفاده از ماژول Http توکار خود، از ماژول جدید InterceptedHttp استفاده کند:
//…
providers: [{
        provide: Http,
        useFactory: (backend: XHRBackend, options: RequestOptions) => {
            return new InterceptedHttp(backend, options);
        },
        deps: [XHRBackend, RequestOptions],
    }],
//…

همه چیز آماده است. اکنون کافی است ماژول Http را در سرویس یا کامپوننت‌های خود تزریق کرده و درخواست‌های Http را بدون هیچگونه نوشتن کد اضافی برای تنظیم هدر و غیره (با فرض اینکه تمامی آنها در متد request از ماژول http نوشته شده‌اند)، به مانند قبل صادر کنید. برای نمونه کد زیر را ببینید.
import { Http, URLSearchParams } from '@angular/http';

//…
constructor(private _http: Http) { }

ngOnInit() {
    let urlSearchParams: URLSearchParams = new URLSearchParams();
    urlSearchParams.append('page', page.toString());
    urlSearchParams.append('count', count.toString());
    let params = urlSearchParams.toString();
    this._http.get(`/cars`, { params: params })
        .subscribe(result => {
            console.log('service: Succ');
            this.cars = result.json();
        }, err => {
            console.log('service: error');
        });
}
//…
با اینکه Angular از interceptor پشتیبانی نمی‌کند، ولی کتابخانه‌هایی برای ایجاد قابلیت مشابه interceptor به وجود آمده‌اند که برخی از آنها عبارتند از:  angular2-cool-http ، ng2-http-interceptor ، ng2-interceptors . به جای extend مستقیم ماژول Http توسط خودتان، اینکار را می‌توانید به این کتابخانه‌ها بسپارید.