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

در شرایطی خاص، تگ script باعث می‌شود که مرورگر برای مدت زمانی متوقف شود و فایل‌های جاوااسکریپت را بارگذاری و اجرا نماید. در این مواقع مرورگر از انجام عملیات دیگری منع شده و شروع به اعمال فایل جاوااسکریپت در حال بارگذاری در محتوای صفحه‌ی بار شده می‌کند. این نگارش و اعمال می‌تواند سبب ویرایش یک element و یا تغییر DOM یا ارجاعی به یک URL دیگر شود. به این منظور، بهترین و رایج‌ترین کار این است که تگ‌های script را به انتهای صفحه و پیش از تگ body منتقل کنیم. با این حال نیز ممکن است که مرورگر برای چند ثانیه منتظر دریافت پاسخ‌های اسکریپت‌های انتهای صفحه بماند، اما این امر چندان قابل توجه نیست؛ به این دلیل که غالب استایل‌ها و content صفحه بارگذاری شده‌اند. با این وجود، این راهکار برای صفحات وب کنونی که ممکن است شامل محتوای یک یا چندین مگابایتی باشند، کافی نیست.  
در این مواقع برخی از کد نویس‌ها از روش‌هایی مانند inject کردن تگ script به صورت Ajax استفاده می‌کنند که این روش‌ها از block شدن صفحه جلوگیری می‌کند، اما با این حال این روش‌ها نیز چالش‌های خاص خود مانند نوشتن کد‌های اضافی برای هر اسکریپت و تست‌های مرتبط برای اطمینان از بار شدن و اجرای صحیح اسکریپت‌ها در موقعیت مناسب را دارند که ممکن است روند کد نویسی با استفاده از کتابخانه‌های third-party را بسیار طولانی کند.
برای حل این چالش خرسندم که تکنیکی را که به صورت built-in در HTML5 وجود دارد، به شما معرفی کنم.

استفاده از defer Attribute

 این Attribute اگر درون هر تگ script ایی نوشته شود، آن فایل در هنگام بارگذاری فایل‌ها و ساختن صفحه، نادیده گرفته می‌شود و پس از اتمام پروسه، این فایل شروع به بارگذاری شدن می‌کند. در حقیقت در این حین در مرورگر همانند تمامی عملیات‌های Lazy Loading به صورت ضمنی یک promise ساخته می‌شود. البته گفتنی است که این نوع بارگذاری نباید شامل فایل‌های جاوا اسکریپتی باشد که شامل عملیاتی مانند document.write و یا تغییرات DOM هستند.
<script src="file.js" defer></script>
مرورگر تمامی فایل‌های شامل defer Attribute را بدون توقف پردازش‌های صفحه، به صورت موازی بارگذاری می‌نماید. اما اینکه این ویژگی از چه زمانی در مرورگر‌ها وجود داشته؟ باید گفت که تقریبا 12 سال از عمر آن می‌گذرد. تقریبا از نسخه‌ی IE4 ( ^_^ ). البته این نکته گفتنی است هنگامی که شما از این Attribute استفاده می‌کنید، این تضمین وجود دارد که تمامی این اسکریپت‌ها اجرا می‌شوند، اما اینکه کدامیک در چه زمانی بارگذاری و اجرا می‌شوند، کاملا نامشخص است. از نظر تئوری، عملیات بارگذاری پس از ویرایش DOM و کمی پیش از فراخوانی رخداد DOMContentLodaded صورت می‌گیرد. اما در عمل، این عملیات به مرورگر و سیستم‌عامل نیز وابسته است.

استفاده از async Attribute

این Attribute در HTML5 معرفی شد و به نوعی کامل کننده و منسوخ کننده‌ی defer می‌باشد.
<script src="file.js" async></script>
به طور کلی می‌توان گفت که عملکرد async و defer یکسان می‌باشند. البته تفاوت اصلی این دو سینتکس، در زمان اجرای اسکریپت‌های بار شده می‌باشد. در async عملیات execution، در اولین فرصتی که پس از تکمیل دریافت فایل ممکن است، صورت می‌گیرد. به سه تصویر زیر توجه کنید:

تصویر فوق یک سناریوی ساده‌ی بارگذاری صفحه‌ی html را به همراه یک فایل جاوااسکریپت با تگ script ساده را نمایش می‌دهد. همانطور که ملاحظه می‌کنید، در لحظه‌ای که فایل اسکریپت شروع به دانلود شدن می‌کند (قسمت بنفش)، عملیات parse صفحه متوقف شده تا زمانی که فایل دانلود و اجرا شود (قسمت قرمز). پس از آن دوباره عملیات parsing از سر گرفته می‌شود. این عملیات به همراه تگ script ساده صورت گرفته است. حال در تصویر بعد قصد داریم که همین سناریو را به کمک defer Attribute بررسی کنیم:

همانطور که ملاحظه می‌کنید به کمک defer، شروع عملیات دانلود فایل اسکریپت، خللی در عملیات html parse ایجاد نمی‌کند و این دو عملیات به صورت موازی می‌توانند اجرا شوند. پس از دریافت فایل اسکریپت، این فایل مستقیما اجرا نمی‌شود و لزوما باید پس از عملیات parsing صورت پذیرد.

و اما تصویر زیر، سناریو‌ی فوق را به همراه async attribute نمایش می‌دهد:

در این حالت عملیات دریافت فایل اسکریپت همانند defer به صورت موازی اجرا می‌شود. اما تفاوتی که در این قسمت و در async وجود دارد، زمان اجرا شدن فایل‌های اسکریپت است. همانطور که ملاحظه می‌کنید دقیقا پس از دریافت فایل، اسکریپت ما در اجراست و پس از اجرای کامل، دوباره عملیات parsing ادامه می‌یابد. در حقیقت اسکریپت‌های با async attribute با فراخوانی رخداد onload شروع به اجرا می‌کنند. جهت استفاده‌ی از async، خیالتان راحت باشد؛ با توجه به این منبع، تقریبا تمام مرورگرها از این ویژگی پشتیبانی می‌کنند.

مطالب
استفاده از چند فرم در کنار هم در ASP.NET MVC

اجرای این نوع صفحات کار سختی نیست؛ با کمی جستجو در اینترنت مثلا در اینجا میتوانید چیزهای خوبی پیدا کنید. اما متاسفانه اکثر مثال‌ها چیزی شبیه قرار دادن پارشال "ورود اعضا" در کنار پارشال "ثبت نام" هستند. حتما متوجه شده‌اید که معمولا این دو صفحه پس از  PostBack به صفحه‌ای جدید هدایت میشوند و یا در بهترین حالت به کمک Ajax ، پس از انجام عملیات، پیامی به کاربر نمایش میدهیم.

در این مقاله سعی شده روشی برای ایجاد چند فرم در یک View توضیح داده شود با این شرط که: 

اولا : از Ajax یا هلپر ایجکسی استفاده نکنیم.

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

 در این روش به این نکته توجه شده که هر مدل پس از Post-back حفظ شود و مستقل از دیگری رفتار کند. مثلا اگر یکی از فرم‌ها ناقص پر شد و دکمه‌ی ارسال آن فشرده شد، پس از Post-back، فقط و فقط اجزای همین فرم Validate شود و فرم دوم بدون تغییر باقی بماند. 

ویوی زیر را در نظر بگیرید. در layout، دو پارشال، به کمک اکشن‌متد فراخوانی شده‌اند:

ViewModelهای مرتبط با این دو بخش به شکل زیر هستند : 

ContactVM .cs  

public class ContactVM
    {
        [Display(Name = "نام")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string Name { get; set; }

              [EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string EmailAddress { get; set; }

        [Display(Name = "متن پیام")]
        [Required(ErrorMessage = "حرفی برای گفتن ندارید؟")]
        public string Description { get; set; }

        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        [Display(Name = "حاصل جمع")]
        public string Captcha { get; set; }
    }

SubscriberVM .cs

    public class SubscriberVM
    {   
        /*[RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "آدرس ایمیل صحیح نیست")]*/
          [EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")] /*.Net4.5*/
        [Display(Name = "ایمیل")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string Email { get; set; }

        [Display(Name = "وضعیت")]
        public bool IsActive { get; set; }    
    }

در Layout، دو اکشن متد صدا زده شده‌اند که وظیفه ارسال ویوهای هر کدام به Layout را به عهده دارند :

        <div class="row footerclass">
            <div class="col-md--6">
                @Html.Action("Subscribers", "Home")
            </div>
            <div class="col-md-6">
                @Html.Action("Contact", "Home")
            </div>

        </div>

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

public ActionResult Contact()
        {
            return PartialView("_Contact", model);
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]

        public ActionResult Contact(ContactVM model)
        {
              if (ModelState.IsValid)
                {
//Do Something                    
                }
            return PartialView("_Contact", model);
        }

        public ActionResult Subscribers()
        {
            return PartialView("_Subscribers");
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Subscribers(SubscriberVM model)
        {
                if (ModelState.IsValid)
                {
//Do Something
                }
            }
            return PartialView("_Subscribers",model);
        }

و اما ویوهایی که قرار است نمایش داده شوند:

Contact.Cshtml

@model IrsaShop.Models.ViewModel.ContactVM


<span></span><span>تماس با ما</span>
<hr />

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    <div>
        @Html.TextBoxFor(m => m.Name, new { @class = "form-control", @id = "name", @name = "name", placeholder = "نام" })
        @Html.ValidationMessageFor(m => m.Name)
    </div>
    <div>
        @Html.TextBoxFor(m => m.EmailAddress, new { @class = "form-control", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr" })
        @Html.ValidationMessageFor(m => m.EmailAddress)
    </div>
    <div>
        @Html.TextAreaFor(model => model.Description, new { @class = "form-control", @id = "message", @name = "message", placeholder = "پیام", @style = "max-width: 100%;height: 90px;" })
    </div>
    <div>
        <input type="button" value="" id="refresh" />
        <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" />
    </div>
    <div>
        @Html.TextBoxFor(model => model.Captcha, new { @class = "form-control", placeholder = "حاصل جمع؟" })
        @Html.ValidationMessageFor(model => model.Captcha)

    </div>
    <div>
        <input type="submit" value="ارسال" name="submitValue" />
    </div>
}

_Subscriber.Csh tml 

@model IrsaShop.Models.SubscriberVM

<span></span><span>خبرنامه</span>
<hr/>

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    <div>

        <div>
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control right-buffer top-buffer pull-right", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr;width: 50%", @required = "required" })
       @*     <button type="submit" name="submitValue">ثبت ایمیل</button>*@
            <input type="submit" value="ثبت ایمیل" name="submitValue" />
        </div>
        
    </div>
   @Html.ValidationMessageFor(m => m.Email,"",new { @class = "right-buffer pull-right"})
   
}

نکته اول : هیچ نوع ورودی برای Html.BeginForm در نظر گرفته نشده است. اگر اکشن متدی را برای صدا زدن در این بخش در نظر بگیرید، هنگام Postback به مشکل برخورد خواهید کرد؛ چون آدرس آن اکشن متد به شکل صریح در آدرس مرورگر فراخوانی میشود و پارشال ما پس از Post-back به تنهایی و بدون Layout نمایش داده خواهد شد. اسم بردن از اکشن متد وقتی کارساز است که آن اکشن متد قرار باشد یک Redirect انجام دهد ولی هدف ما این است که صفحه را از دست ندهیم و پیام‌های خطای ModelState را در همان صفحه قبل و پس از Post-back ببینیم و همچنین پس از انجام عملیات (مثلا ارسال پیام) همین صفحه نمایش داده شود. 

نکته دوم : نکته اول یک مشکل دارد! اگر به شکل صریح اکشن متد مربوط به Post-back مشخص نشود، بطور اتوماتیک تمامی اکشن متدهایی که ویژگی [HttpPost] دارند اجرا خواهند شد. این یعنی هر دو اکشن متد Contact و Subscriber اجرا می‌شوند و بنابر آنچه در اکشن متدها نوشته‌ایم هر دو ModelState بررسی می‌شود که این هدف ما نیست. مثلا فرم سمت چپ را تکمیل کرده ایم و دکمه "ثبت ایمیل" را فشار داده‌ایم و صفحه Postback می‌شود و با اینکه ایمیل در بانک ثبت شده اما فرم سمت راستی با خطا ظاهر میشود که چرا فیلدها خالی هستند!؟ 

برای حل این مشکل کافیست خاصیت name مربوط به دکمه‌ها را به شکل یک ورودی برای اکشن متدها بفرستیم و بر اساس وضعیت آن تنها state مدل مورد نظر خودمان را بررسی کنیم. پس اصلاح زیر را برای اکشن متدهای دارای ویژگی [HttpPost] انجام میدهیم.

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]

        public ActionResult Contact(ContactVM model, , string submitValue)
        {
   if (submitValue == "ارسال") 
                {
                 if (ModelState.IsValid)
                {
//Do Something                    
                }
}   else
                {
                         ModelState.Clear();
                }        
            return PartialView("_Subscribers", model);
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
public ActionResult Subscribers(SubscriberVM model, string submitValue)
        {
             if (submitValue == "ثبت ایمیل") 
            {
if (ModelState.IsValid)
                {
//Do Something
                }
}
            else
            {
                ModelState.Clear();
            }
            return PartialView("_Subscribers");
        }

نکته سوم : در این روش سعی کنید از ViewModel  استفاده کنید و سعی کنید ویو مدل‌ها پراپرتی‌های با نام یکسان نداشته باشند. مثلا پراپرتی Email  در ویو مدل‌ها نام‌های متفاوتی داشته باشند (مثل EmailAddress  ، Email  ، ContactMail  و ...). با اینکار در زمان Postback  احتمال اینکه فیلدهای مشترک اتوماتیک پر شده به ما نمایش داده باشند صفر خواهد شد.

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

نکته پنجم : میتوانید به سادگی مدیریت خطا را به کمک جی کوئری انجام دهید؛ مثلا فرض کنید میخواهیم اگر ایمیل کاربر برای دریافت خبرنامه با موفقیت ثبت شد، پیامی مبنی بر موفقیت برای وی بفرستیم؛ اکشن متد HttPost مربوط به  Subscriber  را به شکل زیر تکمیل میکنیم : 

[HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Subscribers(SubscriberVM model, string submitValue)
        {
            if (submitValue == "ثبت ایمیل")
            {                
                if (ModelState.IsValid)
                {
                    Subscriber mail = new Subscriber() { Email = model.EmailSubscriber, IsActive = true };
                    context.Subscribers.Add(mail);
                    context.SaveChanges();
                    ViewBag.info = "ایمیل شما با موفقیت ثبت شد.";
                    ViewBag.color = "alert-success";
                    ModelState.Clear();
                }
            }
            else
            {
                ModelState.Clear();
            }
            
            return PartialView("_Subscribers ");
        }

در انتهای پارشال _Subscriber هم چند خط کد زیر را مینویسیم :

@if (!String.IsNullOrEmpty(ViewBag.info))
{
    <div id="info" style="position: fixed; bottom: 0; right: 0; margin-right: 1%;">

        <div class="alert @ViewBag.color alert-dismissable">
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
            <strong> @ViewBag.info</strong>
        </div>

    </div>
    <script>
        $(function () {
            $("#info").fadeOut(15000);
        });
    </script>
}


نتیجه این خواهد بود که پس از PostBack در صورت موفقیت تصویر زیر را خواهیم دید و 15 ثانیه المان سبزرنگ بوت استرپِ زیر نمایش داده خواهد شد.

این روش نوعی مدیریت میان اکشن متدهای دارای ویژگی HttpPost است و همانطور که گفتیم به علت اینکه پس از Post-Back نیاز به ساختار به هم نخورده‌ی صفحه‌ی قبلی داریم، نمیتوانستیم به شکل صریح، اکشن متد برای Html.BeginForm تعریف کنیم تا این دردسر‌ها را نداشته باشیم.

حین نوشتن این مقاله به علت وجود if ‌های تو در تو، امیدوار بودم که روش‌های بهتری برای اینکار موجود باشند و هنوز هم امیدوارم نظرات شما چنین چیزی را نشان دهد. 

مطالب
پیاده سازی PWA در Angular
امروزه طراحی اپلیکیشن‌های موبایل بخش زیادی از جامعه را در برگرفته است و روز به روز در حال توسعه میباشند. موازی با رشد روز افزون و نیاز بیشتر به این اپلیکیشن‌ها فریمورک‌های زیادی نیز  ابداع شده اند. از جمله این فریم ورک‌ها می‌توان به موارد زیر اشاره کرد:

Ionic  , react native , flutter , xamarin ….

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

 
مزایای نوشتن یک اپلیکیشن با  فریم ورک‌ها:

1-  نوشتن کد به مراتب آسانتر است.
۲- چون اکثر فریم ورک‌ها، فریم ورک‌های جاوا اسکریپتی هستند، سرعت بالایی هنگام run کردن برنامه دارند ولی در build آخر و خروجی نهایی بر روی پلتفرم، این سرعت کندتر می‌شود.
۳- cossplatform بودن. با طراحی یک اپلیکیشن برای یک پلتفرم می‌توان همزمان برای پلتفرم‌های دیگر خروجی گرفت.


معایب:

۱- برنامه نویس را محدود  میکند.
۲- سرعت اجرای پایینی دارد.
۳- حجم برنامه به مراتب بالاتر میباشد.
۴- از منابع سخت افزاری بیشتری استفاده می‌کند.

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


 pwa چیست؟

pwa مخفف Progressive Web Apps است و یک تکنولوژی برای طراحی وب اپلیکیشن‌های تحت مرورگر میباشد. شما با pwa میتوانید اپلیکیشن خود را بر روی پلتفرم‌های مختلفی اجرا کنید، طوری که کاربران متوجه نشوند با یک صفحه‌ی وبی دارند کار میکنند. شاید شما هم فکر کنید این کار را هم میتوان با html ,css و responsive کردن صفحه انجام داد! ولی اگر بخواهید کاربر متوجه استفاده‌ی از یک صفحه‌ی وبی نشود، باید حتما از pwa استفاده کنید! برای مثال یک صفحه‌ی وبی معمولی حتما باید در بستر اینترنت اجرا شود، ولی با pwa با یکبار وصل شدن به اینترنت و کش کردن داده‌ها، برای بار دوم دیگر نیازی به اینترنت ندارد و میتواند به صورت offline  کار کند. شما حتی با pwa می‌توانید اپلیکیشن را در background اجرا کنید و notification ارسال کنید.


مزیت pwa

۱- یکی از مزیت‌های مهم pwa، حالت offline آن میباشد که حتی با قطع اینترنت، شما میتوانید همچنان با اپلیکیشن کار کنید.
۲- با توجه به اینکه شما در حقیقت با یک صفحه‌ی وبی کار میکنید، دیگر نیازی به دانلود و نصب ندارید.
۳- امکان به‌روز رسانی کردن، بدون اعلام کردن نسخه جدید.
۴- از سرعت بسیار زیادی برخوردار است.
۵- چون pwa از پروتکل https استفاده می‌کند، دارای امنیت بالایی میباشد.


معایب

۱- محدود به مرورگر می‌باشد.
۲- هرچند امروزه اکثر مرورگر‌ها pwa را پشتیبانی میکنند، ولی در بعضی از مرورگر‌ها و مرورگر‌های با ورژن پایین، pwa پشتیبانی نمیشود.
3- نمیتوان یک برنامه‌ی مبتنی بر os را نوشت و محدود به مرورگر میباشد


چند نکته درباره pwa

1- برای pwa  لزومی ندارد حتما از فریم ورک‌های spa استفاده کنید. شما از هر فریم ورک Client Side ای می‌توانید استفاده کنید.
۲- چون صفحات شما در پلتفرم‌های مختلف و با صفحه نمایش‌های مختلفی اجرا می‌شود، باید صفحات به صورت کاملا responsive شده طراحی شوند.
۳- باید از پروتکل https استفاده کنید.

ما در این مقاله از فریم ورک angular  استفاده  خواهیم کرد.

قبل از شروع، با شیوه کار pwa آشنا خواهیم شد. یکی از قسمت‌های مهم  Service Worker ،pwa میباشد که از جمله کش کردن، notification فرستادن و اجرای پردازش‌ها در پس زمینه را بر عهده دارد.


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


چند نکته  در رابطه با Service Worker

- نباید برای  نگهداری داده global از Service Worker استفاد کرد. برای استفاده از داده‌های Global میتوان از Local Storage یا IndexedDB استفاده کرد.
- service worker  به dom دسترسی ندارند.


 سناریو

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


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

1-  سرویس crud را به صورت کامل در پروژه قرار خواهیم داد، ولی چون از حوصله‌ی مقاله خارج است، فقط ثبت کاربران و نمایش کاربران را پیاده سازی خواهیم کرد.
2 - هر اپلیکیشنی قطعا نیاز به یک وب سرویس دارد که واسط بین دیتابیس و اپلیکیشن باشد. ولی ما چون در این مثال به عنوان front end کار قصد طراحی  اپلیکیشن را داریم، برای حذف back end از firebase استفاده خواهیم کرد و مستقیما از انگولار به دیتابیس firebase کوئری خواهیم زد.


 شروع به کار

پیش نیاز‌های یک پروژه‌ی انگیولاری را بر روی سیستم خود فراهم کنید. ما در این مثال از یک template آماده انگیولاری استفاده خواهیم کرد. پس برای اینکه با جزئیات و طراحی ui درگیر نشویم، از لینک github پروژه را دریافت کنید.
سپس وارد root پروژه شوید و با دستور زیر پکیج‌های پروژه را نصب کنید:
 npm install
پس از نصب پکیج‌ها، با دستور ng serve، پروژه را اجرا کنید.

پس از اجرا باید خروجی بالا را داشته باشید. می‌خواهیم یکی از قابلیت‌های pwa را بررسی کنیم. بر روی صفحه کلیک راست کرده و وارد inspect شوید. وارد تب Application  شوید و Service Worker را انتخاب کنید.
 


قبل از اینکه کاری را انجام دهید، چند بار صفحه را refresh کنید! صفحه بدون هیچ تغییری refresh میشود. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید.
این بار صفحه No internet به معنی قطع بودن اینترنت نمایش داده میشود و شما دیگر نمی‌توانید بر روی اپلیکیشن خود فعالیتی داشته باشید! 


نصب pwa  بر روی پروژه

برای اضافه کردن pwa به پروژه وارد ریشه‌ی پروژه شوید و دستور زیر را وارد کنید:
 ng add @angular/PWA
پس از  اجرای دستور بالا، تغییراتی در پروژه لحاظ میشود از جمله در فایل‌هایindex.html,app-module,angular.json، همچنین یک پوشه‌ی جدید به نام icons در assets و دو فایل جیسونی زیر به روت پروژه اضافه شده‌اند:
 Manifest.json : اگر محتویات فایل را مشاهده کرده باشید، شامل تنظیمات فنی وب اپلیکیشن می‌باشد؛ از جمله Home Screen Icon و نام وب اپلیکیشن و سایر تنظیمات دیگر.
 Nsgw-config.json : این فایل نسبت به فایل manifest فنی‌تر میباشد و بیشتر به کانفیگ مد آفلاین و کش کردن مرتبط میشود. در ادامه با این فایل بیشتر کار داریم.

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


  اجرا کردن وب اپلیکیشن

 برای اجرا کردن و نمایش خروجی از وب اپلیکیشن، ابتدا باید از پروژه build گرفت. با استفاده از دستور زیر از پروژه خود build بگیرید:
 ng build -- prod
سپس  با دستور زیر وارد پوشه‌ای که فایل‌های Build  شده در آن قرار دارند، شوید:
 cd dist/Web Application  Pwa
بعد با دستور زیر برنامه را اجرا کنید:
 Http-server -o
اگر چنانچه با دستور بالا به خطا برخوردید، با دستور زیر پکیج npm آن را نصب کنید:
 npm i http-server
اگر تا به اینجای کار درست پیش رفته باشید، پروژه را بدون هیچ تغییری مشاهده خواهیم کرد. inspect گرفته و وارد تب Application شوید. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید! اینبار برنامه بدون هیچ مشکلی کار میکند. حتی شما میتوانید در کنسول، برنامه را به صورت کامل stop کنید.


برسی فایل Nsgw-config.json 

وارد فایل Nsgw-config.json شوید: 

"$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }

    }
  ],
  "dataGroups": [
    {
      "name": "api-performance",
      "urls": [
        "https://api/**"
      ],
      "cacheConfig": {
        "strategy": "performance",
        "maxSize": 100,
        "maxAge": "3d"
      }
    }
  ]
}
 این فایل مربوط به کش کردن داده‌ها میباشند و میتواند شامل چند object زیر باشد. مهمترین آنها را بررسی خواهیم کرد:
  ۱- assetGroups : کش کردن اطلاعات مربوط به اپلیکیشن
  ۲- Index : کش کردن  فایل مربوط به index.html
  ۳-  assetGroups : کش کردن فایل‌های مربوط به asset، شامل فایل‌های js، css  و  غیره
  ۴- dataGroups : این object مربوط به وقتی است که برنامه در حال اجرا است. میتوان داده‌های در حال اجرای اپلیکیشن را کش کرد. داده‌ی در حال اجرا میتواند شامل فراخوانی apiها باشد. برای مثال فرض کنید شما در حالت کار کردن online با اپلیکیشن، لیستی از اسامی کاربران را از api گرفته و نمایش میدهید. وقتی دفعه‌ی بعد در حالت offline  اپلیکیشن را باز کنیم، اگر api را کش کرده باشیم، اپلیکیشن داده‌ها را از کش فراخوانی میکند. این عمل درباره post کردن داده‌ها هم صدق میکند.

خود dataGroups شامل چند object زیر میباشد:
  ۱- name :  یک نام انتخابی برای Groups  میباشد.
 ۲- urls  : شامل آرایه‌ای از آدرس‌ها میباشد. میتوان آدرس یک دومین را همراه با کل apiها به صورت زیر کش کرد:
"https://api/**"
۳- cacheConfig  : که شامل
  ۱- maxSize  : حداکثر تعداد کش‌های مربوط به Groups .
  ۲- maxAge  : حداکثر  lifetime مربوط به کش.
  ۳- strategy  : که میتواند یکی از مقادیر freshness به معنی Network-First یا performance  به معنی Cache-First باشد.


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

در اپلیکیشن‌های native  وقتی به‌روزرسانی جدیدی برای app اعلام میشود، در فروشگاهای اینترنتی پیغامی مبنی بر به‌روزرسانی جدید app برای کاربران ارسال میشود که کاربران میتوانند app خود را به‌روزرسانی کنند. ولی در pwa تنها با یک رفرش صفحه میتوان اپلیکیشن را به جدیدترین امکانات به‌روزرسانی کرد! برای اینکه بتوانیم با هر تغییر، پیغامی را جهت به‌روزرسانی نسخه یا هر  پیغامی دیگری را نمایش دهیم، از کد زیر استفاده میکنم:
if(this.swUpdate.isEnabled)
    {
      this.swUpdate.available.subscribe(()=> {

        if(confirm("New Version available.Load New Version?")){
          window.location.reload();
        }
      })
    }


وصل شدن به دیتابیس

برای اینکه بتوانیم یک وب اپلیکیشن کامل را طراحی کنیم، قطعا نیاز به دیتابیس داریم. برای دیتابیس از firebase استفاده میکنیم. قبل از شروع، وارد سایت firebase  شوید. پس از لاگین، بر روی Add Project کلیک کنید. بعد از انتخاب نام مناسب، create Project را انتخاب کنید. ساختار دیتابیس‌های firebase  شبیه nosqlها می‌باشد و بر پایه‌ی json کار میکنند. پس از ساخت پروژه و دریافت کد جاواسکریپتی زیر شروع به طراحی فیلد‌های دیتابیس خواهیم کرد.


در منوی سمت چپ، بر روی database کلیک کنید و یک دیتابیس را در حالت test mode  ایجاد نماید. سپس یک collection را به نام user ایجاد کرده و فیلد‌های زیر را به آن اضافه کنید:
Age :number
Fullname :string
Mobile : string
مرحله‌ی بعد، config پروژه‌ی انگیولاری میباشد. وارد  vscode شوید و با دستور زیر پکیج firebase را اضافه کنید:
  npm install --save firebase @angular/fire
سپس وارد appmodule شوید و آن‌را به صورت زیر کانفیگ کنید:

@NgModule({
  declarations: [AppComponent, RegisterComponent,AboutComponent, UserListComponent],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    HttpClientModule,
    SharedModule,
    AppRoutingModule,
    AngularFireModule.initializeApp(environmentFirebase.firebase),
    AngularFireDatabaseModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
  ],
  providers: [FirebaseService],
  bootstrap: [AppComponent]
})
export class AppModule {}
خط ۱۰ مربوط به pwa است که هنگام نصب pwa اضافه شده‌است و خط ۸ و مربوط به apiKey فایربیس میباشد. می‌شود گفت environmentFirebase.firebase شبیه  connection string می‌باشد که به صورت زیر کانفیگ شده است:
export const environment ={
   production: true,
   firebase: {
     apiKey: "AIz×××××××××××××××××××××××××××××××××8",
     authDomain: "pwaangular-6c041.firebaseapp.com",
     databaseURL: "https://pwaangular-6c041.firebaseio.com",
     projectId: "pwaangular-6c041",
     storageBucket: "pwaangular-6c041.appspot.com",
     messagingSenderId: "545522081966"
   }

}

FirebaseService در قسمت providers مربوط به سرویس crud میباشد. اگر وارد فایل مربوطه شوید، چند عمل اصلی به صورت زیر در آن پیاده سازی شده است:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import * as firebase from 'firebase';
import { AngularFireDatabase } from '@angular/fire/database';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { ThrowStmt } from '@angular/compiler';
@Injectable({
  providedIn: 'root'
})
export class FirebaseService {

  ref = firebase.firestore().collection('users');
  constructor(public db: AngularFireDatabase,public _http:HttpClient) { 
 
  }

  getUsers(): Observable<any> {
    return new Observable((observer) => {
      this.ref.onSnapshot((querySnapshot) => {
        let User = [];
        querySnapshot.forEach((doc) => {
          let data = doc.data();
          User.push({
            key: doc.id,
            fullname: data.fullname,
            age: data.age,
            mobile: data.mobile
          });
        });
        observer.next(User);
      });
    });
  }

  getUser(id: string): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).get().then((doc) => {
        let data = doc.data();
        observer.next({
          key: doc.id,
          title: data.title,
          description: data.description,
          author: data.author
        });
      });
    });
  }

  postUser(user): Observable<any> {
    return new Observable((observer) => {
      
      this.ref.add(user).then((doc) => {
        observer.next({
          key: doc.id,
        });
      });
    });
  }

  updateUser(id: string, data): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).set(data).then(() => {
        observer.next();
      });
    });
  }

  deleteUser(id: string): Observable<{}> {
    return new Observable((observer) => {
      this.ref.doc(id).delete().then(() => {
        observer.next();
      });
    });
  }

getDataOnApi(){
  return this._http.get('https://site.com/api/General/Getprovince')
  .pipe(
      map((res: Response) => {
return res;

      })
  );
}

getOnApi(){
  return  this._http.get("https://site.com/api/General/Getprovince",).pipe(
    map((response:any) => {
       
return  response
    } )
    );
}
}
اگر دقت کرده باشید، خیلی شبیه به کد نویسی سمت سرور میباشد.

با دستورات زیر میتوانید مجددا پروژه را اجرا کنید:
ng build --prod
cd dist/Pwa-WepApp
Http-server -o


تست وب اپلیکیشن بر روی پلتفرم‌های مختلف

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

نکته! توجه کنید apiهای مربوط به firebase را نمیتوان کش کرد.


کد‌های مربوط به این قسمت را میتوانید از این repository دریافت کنید .
مطالب
Angular Interceptors
تا پیش از این به احتمال زیاد با Interceptor‌ها در IOC Container‌ها متفاوت آشنا شدید و برای AOP از آن‌ها استفاده کرده‌اید. در این جا نیز دقیقا همان مفهوم و هدف را دنبال خواهیم کرد؛ اضافه کردن و تزریق کدهای نوشته شده به منطق برنامه. کاربرد Interceptor‌ها در انگولار، زمانی است که قصد داشته باشیم یک سری تنظیمات عمومی  را برای درخواست‌های  http$ انجام دهیم. همچنین می‌توان انجام برخی مراحل مشترک، نظیر اعتبارسنجی یا مدیریت خطاها را نیز توسط Interceptor‌ها انجام دهیم.
سرویس http$ در Angular جهت ارتباط  و تبادل اطلاعات با دنیای Backend مورد استفاده قرار می‌گیرد. حالت هایی بنابر نیاز به وجود می‌آیند که بخواهیم ارسال اطلاعات به سرور و هم چنین پاسخ دریافتی را capture کنیم و قبل از این که داده‌ها در اختیار App قرار گیرد، آن را مورد بررسی قرار دهیم(برای مثال لاگ اطلاعات) یا حتی نوشتن یک HTTP error handling  جهت مدیریت خطاهای به وجود آمده حین ارتباط با سرور (برای مثال خطای 404).
حال با ذکر مثالی این موارد را بررسی می‌کنیم.  برای نوشتن یک Interceptor می‌توان با استفاده از سرویس factory این کار را به صورت زیر انجام داد.
module.factory('myInterceptor', ['$log', function($log) {
    $log.debug('data');

    var myInterceptor = {
        ....
        ....
        ....
    };

    return myInterceptor;
}]);
کد بالا یک Interceptor بسیار ساده است که وظیفه آن لاگ اطلاعات است. در انگولار چهار نوع Interceptor برای سرویس http$ داریم:
»request: قبل از هر فراخوانی سرویس‌های سمت سرور، ابتدا این Interceptor فراخوانی می‌شود و config سرویس http$ در اختیار آن قرار می‌گیرد. می‌توان این تنظیمات را با توجه به نیاز، تغییر داد و نمونه ساخته شده جدید را در اختیار سرویس http$ قرار دهیم.

»response: هر زمان که عملیات فراخوانی سرویس‌های سمت سرور به درستی انجام شود و همراه با آن پاسخی از سرور دریافت شود، این Interceptor قبل از فراخوانی تابع success سرویس http$، اجرا خواهد شد.

»requestError : از آنجا که سرویس http$ دارای مجموعه ای از Interceptor‌ها است و آن‌ها نیز یکی پس از دیگری حین انجام عملیات اجرا می‌شوند، اگر در Request Interceptor قبلی خطایی رخ دهد بلافاصله این Interceptor فراخوانی می‌شود.

»responseError: درست مانند حالت requestInterceptor است؛ فقط خطای مربوطه باید در تابع response باشد.

با توجه به توضیحات بالا کد قبلی را به صورت زیر تعمیم می‌دهیم.
module.factory('myInterceptor',['$q' , '$log', function($q , $log) {   
 $log.debug('data');  

 return {

request: function(config) {

return config || $q.when(config);
},

requestError: function(rejection) {

return $q.reject(rejection);
},

response: function(response) {

return response || $q.when(response);
},

responseError: function(rejection) {

return $q.reject(rejection);
}

}

}]);
برای رجیستر کردن Interceptor بالا به سرویس‌های http$ باید به صورت زیر عمل نمود.
angular.module('myApp')
.config(function($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
});
مطالب
بررسی اجمالی Redis
نام Redis از Remote Dictionary server گرفته شده‌است. Redis یکی از محبوب‌ترین key-value store‌ها می‌باشد و هم چنین توسط برند‌های بزرگ IT جهان استفاده می‌شود. لازم به ذکر است  Amazon Elastic Cache از Redis پشتیبانی می‌کند. Redis یک دیتابیس No SQL است و بر روی مفهوم زوج  کلید-مقدار (key-value ) کار می‌کند. key-value store امکانی را برای ذخیره داده‌ها که Value  نامیده میشود، در یک Key فراهم می‌کند. شما می‌توانید بعدا این داد‌ه‌ها را دریافت کنید، تنها اگر نام دقیق کلیدی را که برای ذخیره داده استفاده کرده‌اید، بدانید.

What Is In-Memory, Key-Value Store  

Key-Value store یک سیستم ذخیره سازی است؛ جایی که داده‌ها به صورت زوج کلید-مقدار ذخیره می‌شوند. وقتی که میگوییم in-memory key-value store (زوج کلید-مقدار مقیم در حافظه)، منظور این است که زوج کلید-مقدار در حافظه اصلی RAM ذخیره می‌شوند. بنابراین می‌توانیم بگوییم Redis داده‌ها را در حافظه به شکل زوج کلید-مقدار ذخیره کرده است. 
در Redis کلید‌ها باید string باشند؛ ولی value ‌ها می‌توانند یک string ، list ، set ، sorted set یا hash باشند. 
 
Advantage And Disadvantage of Redis over DBMS  

Database Management systems همه چیز را در حافظه ثانویه ذخیره می‌کند که باعث می‌شود خواندن و نوشتن عملیات، تا اندازه‌ای کند باشد. این در حالی است که Redis  همه چیز را در حافظه اصلی ذخیره می‌کند و همین موضوع باعث می‌شود که خواندن و نوشتن داده‌ها توسط آن خیلی سریع باشند. 
حافظه اصلی محدود است. بنابراین Redis نمی‌تواند فایل‌های بزرگ یا binary data را ذخیره کند و تنها اطلاعات متنی کوچک را ذخیره می‌کند که نیاز است قابل دسترسی و اصلاح باشند و با نرخ خیلی سریعی قابل درج باشند. اگر تلاش کنیم که داده‌های بیشتری را نسبت به حافظه موجود بنویسیم، در این حالت خطا دریافت خواهیم کرد.

 Redis  RDBMS
Redis  همه چیز را در حافظه اصلی ذخیره می‌کند. RDBMS همه چیز را در حافظه ثانویه ذخیره می‌کند.
در Redis بخاطر ذخیره سازی داده‌ها در حافظه اصلی، خواندن و نوشتن عملیات به شدت سریع می‌باشد. در RDBMS بخاطر ذخیره سازی داده‌ها در حافظه ثانویه، خواندن و نوشتن
عملیات کند است.
حافظه اصلی از نظر size کوچکتر و از لحاظ قیمت نسبت به حافظه ثانویه گرانتر می‌باشد. Redis نمی‌تواند داده‌های بزرگ یا binary data را ذخیره کند.    حافظه ثانویه از نظر size  بزرگتر و از لحاظ قیمت نسبت به حافظه اصلی ارزان‌تر می‌باشد. RDBMS به آسانی می‌تواند با انواع فایل‌ها کار کند.   


Redis Advantages

  • Redis  : Exceptionally fast خیلی سریع است و می‌تواند حدود 110000  ، SET   و 81000 ،  GET را به ازای هر ثانیه انجام دهد.
  • Redis : Supports rich data type بیشتر دیتا تایپ‌ها را  که توسعه دهندگان قبلا آن‌ها را شناخته‌اند، پشتیبانی می‌کند؛ از قبیل string ، list ، set ، sorted set یا hash .
  •  Operations are atomic  : تمام عملیات Redis اتمیک می‌باشند که این اطمینان خاطر را میدهد اگر دو کلاینت به صورت همزمان به آن دسترسی داشته باشند، Redis server مقدار update شده را دریافت خواهد کرد. 
  • Redis : Multi-utility tool یک ابزار چند منظوره است که می‌تواند در برخی از سناریو‌ها استفاده شود از قبیل:  Redis ) messaging-queues , caching   به صورت بومی از Publish/Subscribe پشتیبانی می‌کند ) , هر داده ای با طول عمر کوتاه در Application مانند web application sessions , ... .
 

Redis Single Instance Architecture 

معماری Redis شامل دو پروسه اصلی است: 
1- Redis client
2- Redis Server


Redis client و Redis Server هر دو می‌توانند در یک کامپیوتر یا کامپیوتر‌های متفاوت باشند. Redis server مسئول ذخیره سازی داده‌ها در حافظه می‌باشد. همانطور که متوجه هستیم، Redis همه چیز را در حافظه اصلی ذخیره می‌کند و حافظه اصلی فرار است؛ از این رو زمانیکه Redis server یا کامپیوتر را راه اندازی مجدد (restart) می‌کنیم، همه داده‌های ذخیره شده را از دست خواهیم داد. بنابراین نیازمند یک راه‌حل، جهت ماندگاری datastore می‌باشیم. 


Redis Persistance 
 
سه راه متفاوت وجود دارد که Redis را پایدار می‌کند : RDB ، AOF و دستور SAVE

1-  RDB : RDB Mechanism یک نمونه از تمام داده‌های در حافظه را تهیه و آن‌ها را در حافظه ثانویه ذخیره می‌کند (ذخیره سازی ماندگار) که در یک وقفه مشخص اتفاق می‌افتد. بنابراین این شانس وجود دارد که شما داده‌هایی را از دست بدهید که بعد از آخرین Set , RDB’s snapshot  شده‌اند . 

2-AOF : AOF همه عملیات نوشتن دریافت شده توسط سرور را ثبت می‌کند. بنابراین همه چیز پایدار است. مشکل استفاده از AOF  این است که برای هر عملیات، شروع به نوشتن در دیسک می‌کند و این یک کار هزینه‌بر است و هم چنین اندازه فایل AOF بزرگتر از RDB می‌باشد. 

3-SAVE Command : شما می‌توانید Redis server را مجبور کنید که یک RDB snapshot را ایجاد کند؛ هر زمانکه Redis console client از دستور SAVE استفاده می‌کند.

در ضمن می‌توانید از AOF  و RDB با هم استفاده کنید تا بهترین نتیجه ماندگاری را داشته باشید. 
 
Redis Replication 

Replication یک تکنیک است که کامپیوتر‌ها را درگیر می‌کند تا دسترسی پذیری داده‌ها و تحمل خطا را با ضریب بیشتری امکان پذیر کنند. در یک محیط Replication، کامپیوتر‌ها، داده‌های یکسانی را با یکدیگر به اشتراک می‌گذارند؛ حتی اگر چندین کامپیوتر دچار مشکل شوند، باز هم، همه داده‌ها در دسترس خواهند بود که به صورت Master/Slaves  می‌باشند.


تمام slave‌ها شامل داده‌های یکسانی همانند master می‌باشند. وقتی‌که یک slave جدید در محیط Replication ایجاد می‌شود، master به صورت خودکار همه داده‌ها را با sync ، slave می‌کند.
تمام Query ‌ها به سرور master هدایت می‌شوند و سپس سرور master عملیات را اجرا می‌کند. وقتی‌که یک عملیات نوشتن اتفاق می‌افتد، سرور master داده‌هایی را که به‌تازگی نوشته شده‌اند، در تمام slave‌ها تکثیر می‌کند. 
 اگر اتفاقی در سرور master رخ دهد، تمام داده‌ها از بین می‌روند؛ در این حالت باید یک slave را به master تبدیل کنیم. 

Clustering In Redis 

Clustering یک تکنیک می‌باشد که توسط آن می‌توان داده‌ها را در چندین کامپیوتر تقسیم بندی کرد. فرض کنید که یک سرور Redis را با 64GB حافظه در اختیار داریم. در این حالت می‌توانیم 64GB داده داشته باشیم. اگر  10 تا clustered computer را که هر کدام 64GB حافظه اصلی دارند، داشته باشیم، در این حالت می‌توان 640GB  داده را ذخیره کرد. 
 

در تصویر بالا می‌توانیم ببینیم که داده‌ها در چهار node، ذخیره شده‌اند. هر node یک Redis Server پیکربندی شده می‌باشد؛ به عنوان یک cluster node. اگر یکی از node ‌ها دچار مشکل شوند، سپس کل cluster متوقف می‌شود. 

Redis Client 

وب سایت Try Redis ، یک Redis console client  آنلاین است و به شما کمک می‌کند تا یاد بگیرید چگونه از Redis console client  استفاده کنید.


در قسمت بعد در رابطه با نصب Redis  بر روی سیستم عامل ویندوز و دیتا تایپ‌ها در Redis صحبت خواهیم کرد.
نظرات مطالب
EF Code First #7
یعنی میفرمایید من چه رابطه ای بین این دو کلاس تعریف کنم تا رابطه 0..1-0..1 داشته باشم؟
    public class Order
    {
        public int OrderId { get; set; }
        public virtual Quotation Quotation { get; set; }
    }
    public class Quotation
    {
        public int QuotationId { get; set; }
        public virtual Order Order { get; set; }
    }

نظرات مطالب
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامه‌های Angular
نکته تکمیلی
با کمی تغییرات در بدنه متد processModelStateErrors، می‌توان برای سناریوهای master-detail که خطاهایی به شکل زیر از سمت سرور دریافت می‌شود نیز پیغام متناظر به کنترل‌های موجود در FormArray‌ها را نیز به درستی نمایش داد:
{
  'detail[0].title':['message'],
  'detail[0].detail[0].title':['message']
}
برای این منظور متد findFieldControl را به شکل زیر پیاده سازی خواهیم کرد:
  private findFieldControl(fieldName: string): AbstractControl {
    const path = fieldName.replace('[', '.').replace(']', '');
    return (
      this.form.get(path) || this.form.get(this.lowerCaseFirstLetter(path))
    );
  }
متد get استفاده شده در بدنه متد، امکان دریافت path متناظر با یک کنترل را نیز دارد؛ از این جهت صرفا لازم بود کلید دریافتی از سمت سرور را به شکل زیر تبدیل کنیم:
'detail.0.title'
'detail.0.detail.0.title'
و در نهایت اعمال تغییرات به متد processModelStateErrors به شکل زیر می‌باشد:
protected handleSubmitError(response: HttpErrorResponse) {
    this.messages = [];

    if (response.status === 400) {
      // handle validation errors
      const validationDictionary = response.error;
      for (const fieldName in validationDictionary) {
        if (validationDictionary.hasOwnProperty(fieldName)) {
          const messages = validationDictionary[fieldName];
          const control = this.findFieldControl(fieldName);
          if (control) {
            // integrate into angular's validation if we have field validation
            control.setErrors({
              model: { messages: messages }
            });
          } else {
            // if we have cross field validation, then show the validation error at the top of the screen
            this.messages.push(...messages);
          }
        }
      }
    } else {
      this.messages.push('something went wrong!');
    }
  }

نظرات مطالب
بارگزاری PartialView با استفاده از jQuery در زمان اجرا
باسلام و خسته نباشید. 
من مثال فوق را خط به خط اجرا کردم ولی partial view  نمایش داده نمیشه. فکر کنم مکان  قطعه کد Ajax  را اشتباه جایگذاری کردم اگه ممکنه راهنمائی می‌کنید که قطعه:
 $( function () {
$.ajax({
//مشخص کردن  اکشنی که باید فراخوانی شود
url: '/Home/Details' ,
contentType: 'application/html; charset=utf-8' ,
type: 'GET' ,
//نوع نتیجه بازگشتی
dataType: 'html'
 
})
.success( function (result) {
  //زمانی که کدهای سمت سرور بدون خطا اجرا شده اند
  //این قسمت فراخوانی می‌شود و نتیجه اکشن درون متغیر
  //result
  //قرار می‌گیرد
  $( '#sectionContents' ).html(result);
})
.error( function (xhr, status) {
  alert(xhr.responseText);
});
  });
دقیقا کجای Index باید قرارداده بشه؟ سورس پروژه را هم ارسال می‌کردید خیلی خوب می‌شد .
ممنون. 
نظرات مطالب
کار با اسکنر در برنامه های تحت وب (قسمت دوم و آخر)
سلام؛ ممنون از مطلب مفیدتون. من راهی رو گفتیم رفتم wcfservice هم run کردم و درست بود اما با اجرای WinForm  و  زدن آدرس locallhost:8010 چیزی باز نمیشه. کد زیر هم اصلا کار نمیکنه:
 <script>
  $( "#get-scan" ).click(function () {
       var url = 'http://localhost:8010/' ;
       $. get (url, function (data) {
       $( "#img-scanned" ).attr( "src" , "data:image/Jpeg;base64,  " + data.GetScanResult);
    });
  });
پورت رو هم 6019 میذارم بازم همینطور. ممنون میشم راهنمایی کنید.