مطالب
بازنویسی سطح دوم کش برای Entity framework 6
چندی قبل مطلبی را در مورد پیاده سازی سطح دوم کش در EF در این سایت مطالعه کردید. اساس آن مقاله‌ای بود که نحوه‌ی کش کردن اطلاعات حاصل از LINQ to Objects را بیان کرده بود (^). این مقاله پایه‌ی بسیاری از سیستم‌های کش مشابه نیز شده‌است (^ و ^ و ...).
مشکل مهم این روش عدم سازگاری کامل آن با EF است. برای مثال در آن تفاوتی بین (Include(x=>x.Tags و (Include(x=>x.Users وجود ندارد. به همین جهت در این نوع موارد، قادر به تولید کلید منحصربفردی جهت کش کردن اطلاعات یک کوئری مشخص نیست. در اینجا یک کوئری LINQ، به معادل رشته‌ای آن تبدیل می‌شود و سپس Hash آن محاسبه می‌گردد. این هش، کلید ذخیره سازی اطلاعات حاصل از کوئری، در سیستم کش خواهد بود. زمانیکه دو کوئری Include دار متفاوت EF، هش‌های یکسانی را تولید کنند، عملا این سیستم کش، کارآیی خودش را از دست می‌دهد. برای رفع این مشکل پروژه‌ی دیگری به نام EF cache ارائه شده‌است. این پروژه بسیار عالی طراحی شده و می‌تواند جهت ایده دادن به تیم EF نیز بکار رود. اما در آن فرض بر این است که شما می‌خواهید کل سیستم را در یک کش قرار دهید. وارد مکانیزم DBCommand و DataReader می‌شود و در آن‌جا کار کش کردن تمام کوئری‌ها را انجام می‌دهد؛ مگر آنکه به آن اعلام کنید از کوئری‌های خاصی صرفنظر کند.
با توجه به این مشکلات، روش بهتری برای تولید هش یک کوئری LINQ to Entities بر اساس کوئری واقعی SQL تولید شده توسط EF، پیش از ارسال آن به بانک اطلاعاتی به صورت زیر وجود دارد:
        private static ObjectQuery TryGetObjectQuery<T>(IQueryable<T> source)
        {
            var dbQuery = source as DbQuery<T>;

            if (dbQuery != null)
            {
                const BindingFlags privateFieldFlags = 
                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public;

                var internalQuery =
                    source.GetType().GetProperty("InternalQuery", privateFieldFlags)
                        .GetValue(source);

                return
                    (ObjectQuery)internalQuery.GetType().GetProperty("ObjectQuery", privateFieldFlags)
                        .GetValue(internalQuery);
            }

            return null;
        }
این متد یک کوئری LINQ مخصوص EF را دریافت می‌کند و با کمک Reflection، اطلاعات درونی آن که شامل ObjectQuery اصلی است را استخراج می‌کند. سپس فراخوانی متد objectQuery.ToTraceString بر روی حاصل آن، سبب تولید SQL معادل کوئری LINQ اصلی می‌گردد. همچنین objectQuery امکان دسترسی به پارامترهای تنظیم شده‌ی کوئری را نیز میسر می‌کند. به این ترتیب می‌توان به معادل رشته‌ای منطقی‌تری از یک کوئری LINQ رسید که قابلیت تشخیص JOINها و متد Include نیز به صورت خودکار در آن لحاظ شده‌است.

این اطلاعات، پایه‌ی تهیه‌ی کتابخانه‌ی جدیدی به نام EFSecondLevelCache گردید. برای نصب آن کافی است دستور ذیل را در کنسول پاورشل نیوگت صادر کنید:
 PM> Install-Package EFSecondLevelCache
سپس برای کش کردن کوئری معمولی مانند:
 var products = context.Products.Include(x => x.Tags).FirstOrDefault();
می‌توان از متد جدید Cacheable آن به نحو ذیل استفاده کرد (این روش بسیار تمیزتر است از روش مقاله‌ی قبلی و امکان استفاده‌ی از انواع و اقسام متدهای EF را به صورت متداولی میسر می‌کند):
 var products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too.

پس از آن نیاز است کدهای کلاس Context خود را نیز به نحو ذیل ویرایش کنید (به روز رسانی شده‌ی آن در اینجا):
namespace EFSecondLevelCache.TestDataLayer.DataLayer
{
    public class SampleContext : DbContext
    {
        // public DbSet<Product> Products { get; set; }
 
        public SampleContext()
            : base("connectionString1")
        {
        }
 
        public override int SaveChanges()
        {
            return SaveAllChanges(invalidateCacheDependencies: true);
        }
 
        public int SaveAllChanges(bool invalidateCacheDependencies = true)
        {
            var changedEntityNames = getChangedEntityNames();
            var result = base.SaveChanges();
            if (invalidateCacheDependencies)
            {
               new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames);
            }
            return result;
        }
 
        private string[] getChangedEntityNames()
        {
            return this.ChangeTracker.Entries()
                .Where(x => x.State == EntityState.Added ||
                            x.State == EntityState.Modified ||
                            x.State == EntityState.Deleted)
                .Select(x => ObjectContext.GetObjectType(x.Entity.GetType()).FullName)
                .Distinct()
                .ToArray();
        }
    }
}
متد InvalidateCacheDependencies سبب می‌شود تا اگر تغییری در بانک اطلاعاتی رخ‌داد، به صورت خودکار کش‌های کوئری‌های مرتبط غیر معتبر شوند و برنامه اطلاعات قدیمی را از کش نخواند.


کدهای کامل این پروژه را از مخزن کد ذیل می‌توانید دریافت کنید:
EFSecondLevelCache



پ.ن.
این کتابخانه هم اکنون در سایت جاری در حال استفاده است.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
ارتقاء به ASP.NET Core Identity 3.0

امضای اینترفیس ILookupNormalizer، از حالت کلی زیر، که هم ایمیل و هم نام را شامل می‌شد:
    public interface ILookupNormalizer
    {
        string Normalize(string key);
    }
به حالت اختصاصی‌تر زیر تغییر کرده‌است:
    public interface ILookupNormalizer
    {
        string NormalizeEmail(string email);
        string NormalizeName(string name);
    }
مطالب
بارگزاری PartialView با استفاده از jQuery در زمان اجرا
مزیت استفاده از PartialViewها، ماژولار کردن برنامه است. برای مثال اگر صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا، سخن روز و آمار کاربران تشکیل شود، می‌توان هر کدام را توسط یک PartialView پیاده سازی کرد و سپس صفحه اصلی را از کنار هم قرار دادن این PartialViewها تهیه نمود.(منبع).
در این پست قصد دارم به نحوه بارگزاری یک PartialView  با استفاده از ASP.NET MVC بپردازم.
ابتدا یک پروژه جدید ایجاد کنید. حال می‌خواهیم زمانیکه صفحه اصلی سایت بارگزاری می‌شود، لیست تمام محصولات را نمایش دهد. برای نمایش محصولات یک PartialView جدید را ایجاد می‌کنیم؛ همانند شکل ذیل:

حال برای ساده کردن مثال، متن ثابتی را درون این PartialView می‌نویسیم:

product  List Partial
در ادامه قصد داریم که درIndex، این View را بارگزاری کنیم. برای این کار از متد ajax مربوط به کتابخانه jQuery به صورت زیر استفاده می‌کنیم:
 $(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);
});
    });
و پس از آن محلی را که قرار است PartialView در آن بارگزاری شود، ایجاد می‌کنیم:
<div id="sectionContents"></div>
حال فقط باید اکشن مورد نظر را در HomeController پیاده سازی کنیم:
 public ActionResult Details()
        {
           return PartialView("partial/_ProductList");
        }
 با استفاده از این کد مشخص کردیم که این اکشن، یک PartialView را با نام ProductList_ برگشت می‌دهد. البته در یک مثال واقعی باید لیست محصولات را هم به این PartialView پاس دهیم.
نتیجه اجرا به صورت زیر است:

نظرات مطالب
EF Code First #10
- فضای نام System.Data.Entity مربوط به متد الحاقی جدید Include، در اسمبلی EntityFramework.dll وجود دارد. بنابراین برای استفاده از آن نیاز است اسمبلی EntityFramework.dll را هم به پروژه کتابخانه‌ای جدید خود، الحاق کنید.
- اگر مورد فوق برقرار است و Intellisense کار نمی‌کند، اما برنامه کامپایل می‌شود، احتمالا مشکل از ReSharper ایی است که دارید استفاده می‌کنید. VS.NET را با دستور خط فرمان ذیل، در حالت safe mode اجرا کنید:
"c:\__path to__\IDE\devenv.exe" /safemode /nosplash /log
در این حالت افزونه‌ها بارگذاری نمی‌شوند. اگر مشکلی نبود، یعنی باید ReSharper را به روز یا مجددا نصب کنید.
مطالب دوره‌ها
به روز رسانی غیرهمزمان قسمتی از صفحه به کمک jQuery در ASP.NET MVC
یک صفحه شلوغ و سنگین را در نظر بگیرید. برای مثال قرار است ابتدا مطلب خاصی در سایت نمایش یابد و سپس ادامه صفحه که شامل انبوهی از لیست نظرات مرتبط با آن مطلب است به صورت غیرهمزمان و Ajax ایی بدون توقف پردازش صفحه، در فرصتی مناسب از سرور دریافت و به صفحه اضافه گردد (به روز رسانی قسمتی از صفحه در فرصت مناسب). در این حالت چون نمایش اولیه صفحه سریع صورت می‌گیرد، کاربر نهایی آنچنان احساس کند بودن بازکردن صفحات سایت را نخواهد داشت. در ادامه نحوه پیاده سازی این روش را به کمک jQuery Ajax بررسی خواهیم کرد.

مدل و کنترلر برنامه

namespace jQueryMvcSample07.Models
{
    public class BlogPost
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }
}

using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample07.Models;
using jQueryMvcSample07.Security;

namespace jQueryMvcSample07.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش یک منوی ساده در ابتدای کار
        }

        [HttpGet]
        public ActionResult ShowSynchronous()
        {
            var model = getModel();
            return View(model); //نمایش همزمان
        }

        private static BlogPost getModel()
        {
            //شبیه سازی یک عملیات طولانی
            System.Threading.Thread.Sleep(3000);
            var model = new BlogPost
            {
                Title = "عنوان ... ",
                Body = "مطلب... "
            };
            return model;
        }

        [HttpGet]
        public ActionResult ShowAsynchronous()
        {
            return View(); //نمایش ابتدایی صفحه
        }

        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult RenderAsynchronous()
        {
            //دریافت اطلاعات به صورت غیرهمزمان
            var model = getModel();
            return PartialView(viewName: "_Post", model: model);
        }
    }
}
مدل برنامه، بیانگر ساختار اطلاعات مطلبی است که قرار است نمایش یابد.
در کنترلر Home، ابتدا اکشن متد Index آن فراخوانی شده و در این حالت دو لینک زیر نمایش داده می‌شوند:
@{
    ViewBag.Title = "Index";
}
<h2>
    نمایش اطلاعات به صورت همزمان و غیرهمزمان</h2>
<ul>
    <li>
        @Html.ActionLink(linkText: "نمایش همزمان", actionName: "ShowSynchronous", controllerName: "Home")
    </li>
    <li>
        @Html.ActionLink(linkText: "نمایش غیر همزمان", actionName: "ShowAsynchronous", controllerName: "Home")
    </li>
</ul>
لینک اول، به اکشن متد ShowSynchronous اشاره می‌کند و لینک دوم به اکشن متد ShowAsynchronous.
در هر دو صفحه نهایتا از یک Partial View به نام _Post.cshtml برای نمایش اطلاعات استفاده خواهد شد:
@model jQueryMvcSample07.Models.BlogPost
<fieldset>
    <legend>@Model.Title</legend>
    @Model.Body
</fieldset>
زمانیکه کاربر بر روی لینک نمایش همزمان کلیک می‌کند، به صفحه زیر هدایت می‌شود:
@model jQueryMvcSample07.Models.BlogPost
@{
    ViewBag.Title = "ShowSynchronous";
}

<h2>نمایش همزمان</h2>
@{ Html.RenderPartial("_Post", Model); }
این صفحه، یک صفحه متداول است و اطلاعات آن دقیقا در زمان نمایش صفحه اخذ شده و چون در اینجا از یک Sleep عمدی برای تولید اطلاعات استفاده گردیده است، نمایش آن حداقل سه ثانیه طول خواهد کشید.
در حالتیکه کاربر بر روی لینک نمایش غیرهمزمان کلیک می‌کند، صفحه زیر را مشاهده خواهد کرد:
@{
    ViewBag.Title = "ShowAsynchronous";
    var loadInfoUrl = Url.Action(actionName: "RenderAsynchronous", controllerName: "Home");
}
<h2>
    نمایش غیر همزمان</h2>
<div id="info" align="center">
</div>
<div id="progress" align="center" style="display: none">
    <br />
    <img src="@Url.Content("~/Content/images/loadingAnimation.gif")" alt="loading..."  />
</div>
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#progress").css("display", "block");
            $.ajax({
                type: "POST",
                url: '@loadInfoUrl',
                complete: function (xhr, status) {
                    var data = xhr.responseText;
                    if (xhr.status == 403) {
                        window.location = "/login";
                    }
                    else if (status === 'error' || !data || data == "nok") {
                        alert('خطایی رخ داده است');
                    }
                    else {
                        $("#progress").css("display", "none");
                        $("#info").html(data);
                    }
                }
            });
        });
    </script>
}
نمایش ابتدایی این صفحه بسیار سریع است. در ابتدای کار progress bar ایی فعال شده و سپس از طریق jQuery Ajax درخواست دریافت اطلاعات رندر شده اکشن متدی به نام RenderAsynchronous به سرور ارسال می‌شود. چون عملیات Ajax غیرهمزمان است، کاربر نیازی نیست تا رندر شدن کامل صفحه ابتدا صبر کند و سپس کل صفحه به او نمایش داده شود. در اینجا ابتدا صفحه به صورت کامل نمایان شده و سپس درخواستی Ajax ایی به سرور ارسال می‌گردد. در پایان عملیات، Partial View یاد شده رندر گردیده و در div ایی با id مساوی info نمایش داده می‌شود.
به این ترتیب می‌توان حس سریع بودن سایت را زمانیکه قسمتی از صفحه نیاز به زمان بیشتری برای نمایش اطلاعات دارد، به کاربر منتقل کرد.

دریافت پروژه کامل این قسمت
jQueryMvcSample07.zip 
مطالب
آشنایی با M.A.F - قسمت دوم

قسمت قبل بیشتر آشنایی با یک سری از اصطلاحات مرتبط با فریم ورک MAF بود و همچنین نحوه‌ی کلی استفاده از آن. در این قسمت یک مثال ساده را با آن پیاده سازی خواهیم کرد و فرض قسمت دوم بر این است که افزونه‌ی Visual Studio Pipeline Builder را نیز نصب کرده‌اید.

یک نکته پیش از شروع:
- اگر افزونه‌ی Visual Studio Pipeline Builder پس از نصب به منوی Tools اضافه نشده است، یک پوشه‌ی جدید را به نام Addins در مسیر Documents\Visual Studio 2008 ایجاد کرده و سپس فایل‌های آن‌را در این مسیر کپی کنید.

ساختار اولیه یک پروژه MAF

- پروژ‌ه‌هایی که از MAF استفاده می‌کنند، نیاز به ارجاعاتی به دو اسمبلی استاندارد System.AddIn.dll و System.AddIn.Contract.dll دارند (مطابق شکل زیر):



- ساختار آغازین یک پروژه MAF از سه پروژه تشکیل می‌شود که توسط افزونه‌ی Visual Studio Pipeline Builder به 7 پروژه بسط خواهد یافت.
این سه پروژه استاندارد آغازین شامل موارد زیر هستند:



- هاست: همان برنامه‌ی اصلی که قرار است از افزونه استفاده کند.
- قرار داد: نحو‌ه‌ی تعامل هاست و افزونه در این پروژه تعریف می‌شود. (یک پروژه از نوع class library)
- افزونه: کار پیاده سازی قرار داد را عهده دار خواهد شد. (یک پروژه از نوع class library)

- همچنین مرسوم است جهت مدیریت بهتر خروجی‌های حاصل شده یک پوشه Output را نیز به این solution اضافه کنند:



اکنون با توجه به این محل خروجی، به خواص Build سه پروژه موجود مراجعه کرده و مسیر Build را اندکی اصلاح خواهیم کرد (هر سه مورد بهتر است اصلاح شوند)، برای مثال:



نکته‌ی مهم هم اینجا است که خروجی host باید به ریشه این پوشه تنظیم شود و سایر پروژه‌ها هر کدام خروجی خاص خود را در پوشه‌ای داخل این ریشه باید ایجاد کنند.



تا اینجا قالب اصلی کار آماده شده است. قرارداد ما هم به شکل زیر است (ویژگی AddInContract آن نیز نباید فراموش شود):

using System.AddIn.Pipeline;
using System.AddIn.Contract;

namespace CalculatorConract
{
[AddInContract]
public interface ICalculatorContract : IContract
{
double Operate(string operation, double a, double b);
}
}

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

ایجاد pipeline

اگر قسمت قبل را مطالعه کرده باشید، یک راه حل مبتنی بر MAF از 7 پروژه تشکیل می‌شود که عمده‌ترین خاصیت آن‌ها مقاوم کردن سیستم در مقابل تغییرات نگارش قرارداد است. در این حالت اگر قرار داد تغییر کند، نه هاست و نه افزونه‌ی قدیمی، نیازی به تغییر در کدهای خود نخواهند داشت و این پروژه‌های میانی هستند که کار وفق دادن (adapters) نهایی را برعهده می‌گیرند.


برای ایجاد خودکار View ها و همچنین Adapters ، از افزونه‌ی Visual Studio Pipeline Builder که پیشتر معرفی شد استفاده خواهیم کرد.



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


پس از ایجاد این پروژه‌ها، نیاز به اصلاحات مختصری در مورد نام اسمبلی و فضای نام هر کدام می‌باشد؛ زیرا به صورت پیش فرض هر کدام به نام template نامگذاری شده‌اند:



پیاده سازی افزونه

قالب کاری استفاده از این فریم ورک آماده است. اکنون نوبت به پیاده سازی یک افزونه می‌باشد. به پروژه AddIn مراجعه کرده و ارجاعی را به اسمبلی AddInView خواهیم افزود. به این صورت افزونه‌ی ما به صورت مستقیم با قرارداد سروکار نداشته و ارتباطات، در راستای همان pipeline تعریف شده، جهت مقاوم شدن در برابر تغییرات صورت می‌گیرد:
using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
throw new NotImplementedException();
}
}
}

در اینجا افزونه‌ی ما باید اینترفیس ICalculator مربوط به AddInView را پیاده سازی نماید که برای مثال خواهیم داشت:

using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn("افزونه یک", Description = "توضیحات", Publisher = "نویسنده", Version = "نگارش یک")]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
switch (operation)
{
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
default:
throw new NotSupportedException("عملیات مورد نظر توسط این افزونه پشتیبانی نمی‌شود");
}
}
}
}

همانطور که در قسمت قبل نیز ذکر شد، این کلاس باید با ویژگی AddIn مزین شود که توسط آن می‌توان توضیحاتی در مورد نام ، نویسنده و نگارش افزونه ارائه داد.


استفاده از افزونه‌ی تولید شده

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

نکته‌ی مهم!
در هر دو ارجاع به HostView و یا AddInView باید خاصیت Copy to local به false تنظیم شود، در غیر اینصورت افزونه‌ی شما بارگذاری نخواهد شد.



پس از افزودن ارجاعی به HostView، نمونه‌ای از استفاده از افزونه‌ی تولید شده به صورت زیر می‌تواند باشد که توضیحات مربوطه به صورت کامنت آورده شده است:

using System;
using System.AddIn.Hosting;
using CalculatorConract.HostViews;

namespace Calculator
{
class Program
{
private static ICalculator _calculator;

static void doOperation()
{
Console.WriteLine("1+2: {0}", _calculator.Operate("+", 1, 2));
}

static void Main(string[] args)
{
//مسیر پوشه ریشه مربوطه به خط لوله افزونه‌ها
string path = Environment.CurrentDirectory;

//مشخص سازی مسیر خواندن و کش کردن افزونه‌ها
AddInStore.Update(path);

//یافتن افزونه‌هایی سازگار با شرایط قرارداد پروژه
//در اینجا هیچ افزونه‌ای بارگذاری نمی‌شود
var addIns = AddInStore.FindAddIns(typeof(ICalculator), path);

//اگر افزونه‌ای یافت شد
if (addIns.Count > 0)
{
var addIn = addIns[0]; //استفاده از اولین افزونه
Console.WriteLine("1st addIn: {0}", addIn.Name);

//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
_calculator = addIn.Activate<ICalculator>(AddInSecurityLevel.Intranet);

//یک نمونه از استفاده آن
doOperation();
}

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

چند نکته جالب توجه در مورد قابلیت‌های ارائه شده:
- مدیریت load و unload پویا
- امکان تعریف سطح دسترسی و ویژگی‌های امنیتی اجرای یک افزونه
- امکان ایزوله سازی پروسه اجرای افزونه از هاست (در ادامه توضیح داده خواهد شد)
- مقاوم بودن پروژه به نگارش‌های مختلف قرارداد


اجرای افزونه در یک پروسه مجزا

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

//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
//همچنین جدا سازی پروسه اجرایی افزونه از هاست
_calculator = addIn.Activate<ICalculator>(
new AddInProcess(),
AddInSecurityLevel.Intranet);

در این حالت اگر پس از فعال شدن افزونه، یک break point قرار دهیم و به task manager ویندوز مراجعه نمائیم، پروسه‌ی مجزای افزونه قابل مشاهده است.



برای مطالعه بیشتر + ، + ، + و +

مطالب
Includeهای صرفنظر شده در EF Core
یکی از روش‌های Join نوشتن درEF ، استفاده از Includeها است. اما ... آیا تمام Includeهایی که تعریف شده‌اند ضروری بوده‌اند؟ آیا تمام Joinهای حاصل از Include‌های تعریف شده مورد استفاده قرار گرفته‌اند و بی‌جهت کارآیی برنامه را پایین نیاورده‌اند؟ EF Core برای مقابله با یک چنین مواردی که بسیار هم متداول هستند، پس از بررسی کوئری، سعی می‌کند Includeهای اضافی و بی‌مصرف را حذف کرده و سبب تولید Joinهای اضافی نشود.


یک مثال: بررسی تنظیمات نمایش خطاهای Includeهای صرفنظر شده

موجودیت‌های Blog و Postهای آن‌را درنظر بگیرید:
public class Blog
{
  public int BlogId { get; set; }
  public string Url { get; set; }

  public List<Post> Posts { get; set; }
}

public class Post
{
  public int PostId { get; set; }
  public string Title { get; set; }
  public string Content { get; set; }

  public int BlogId { get; set; }
  public Blog Blog { get; set; }
}

به همراه Context آن که به صورت ذیل تعریف شده‌است:
    public class BloggingContext : DbContext
    {
        public BloggingContext()
        { }

        public BloggingContext(DbContextOptions options)
            : base(options)
        { }

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Demo.Includes;Trusted_Connection=True;");
                optionsBuilder.ConfigureWarnings(warnings => warnings.Log(CoreEventId.IncludeIgnoredWarning));
                optionsBuilder.UseLoggerFactory(new LoggerFactory().AddConsole((message, logLevel) =>
                {
                    return true;
                    /*return logLevel == LogLevel.Debug &&
                           message.StartsWith("Microsoft.EntityFrameworkCore.Database.Command");*/
                }));
            }
        }
    }
در اینجا در تنظیمات ConfigureWarnings، نمایش IncludeIgnoredWarning نیز لحاظ شده‌است. به این ترتیب می‌توان از وقوع Includeهای صرفنظر شده مطلع شد.
همچنین ذکر UseLoggerFactory، به نحوی که مشاهده می‌کنید، یکی دیگر از روش‌های لاگ کردن خروجی‌های EF است. در اینجا اگر true تنظیم شود، تمام اتفاقات مرتبط با EF Core لاگ می‌شوند. اگر می‌خواهید تنها کوئری‌های SQL آن‌را مشاهده کنید، روش فیلتر کردن آن‌ها در سطر بعدی نمایش داده شده‌است.

در این حالت اگر کوئری ذیل را اجرا کنیم:
var blogs = context.Blogs
    .Include(blog => blog.Posts)
    .Select(blog => new
    {
       Id = blog.BlogId,
       Url = blog.Url
    })
    .ToList();
ابتدا خطای ذیل نمایش داده می‌شود:
 warn: Microsoft.EntityFrameworkCore.Query[100106]
The Include operation for navigation '[blog].Posts' is unnecessary and was ignored because the navigation is not reachable in the final query results. See https://go.microsoft.com/fwlink/?linkid=850303 for more information.
سپس کوئری نهایی تشکیل شده نیز به صورت ذیل است:
SELECT [blog].[BlogId] AS [Id], [blog].[Url]
FROM [Blogs] AS [blog]
همانطور که ملاحظه می‌کنید در اینجا خبری از join به جدول posts نیست و یک کوئری ساده را تشکیل داده‌است. چون خروجی نهایی که در Select قید شده‌است، ارتباطی به جدول posts ندارد. به همین جهت، تشکیل join بر روی آن غیرضروری است و با این خطای ذکر شده می‌توان دریافت که بهتر است Include ذکر شده را از کوئری حذف کرد.


یک نکته: اگر تنظیم نمایش IncludeIgnoredWarning را به نحو ذیل به Throw تنظیم کنیم:
 optionsBuilder.ConfigureWarnings(warnings => warnings.Throw(CoreEventId.IncludeIgnoredWarning));
اینبار برنامه با رسیدن به یک چنین کوئری‌هایی، متن اخطار ذکر شده را به صورت یک استثناء صادر خواهد کرد. این حالت در زمان توسعه‌ی برنامه می‌تواند مفید باشد.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: IgnoredIncludes.zip
مطالب
xamarin.android قسمت دوم
در بحث گذشته کنترل‌های مورد نظر را بصورت داینامیک تولید کردیم که در طراحی Appهای پیچیده مناسب نمی‌باشد و بهتر است فرم و طراحی گرافیکی را از قبل آماده کرده و در activity اجرا نماییم. به فرم‌های از قبل طراحی شده، layout گفته میشود. layout‌ها با فرمت xml ساخته می‌شوندو بنابراین به زبان سی شارپ مربوط نمی‌باشد.
   
در زامارین 2 نوع layout داریم
1: صفحات razor از قبل پردازش شده PreProcessRazorPaged
2: layout استاندارد اندروید با ساختار xml که در زامارین ساخته میشود. در اینجا یک طراح جهت طراحی گرافیکی اندروید نصب میشود که خروجی را به xml تبدیل میکند.
  
محل استاندارد طراحی layout
در دایرکتوری resource بر روی دایرکتوری layout راست کلیک نموده و add new item و سپس android layout را انتخاب می‌کنیم که در این حالت یک فایل با پسوند xml اضافه میشود. با انتخاب layout و زدن دکمه f4، پنجره properties باز شده و میتوانیم خصوصیات layout را تغییر دهیم.
پس از ایجاد layout که دستورات غیر اجرایی (مرده) می‌باشد، بایستی آن‌ها به کلاس‌های کنترل معادل خود در اندروید تبدیل شوند که به این عملیات inflating و یا inflation نیز گفته میشود. پس از عملیات inflating می‌توان کنترل‌ها را پیدا کرده و آنها را برنامه نویسی کنیم.

FindViewbyid در پارامتر ورودی خود از طریق Resource.id، نام کنترل را دریافت نموده و بصورت Object باز می‌گرداند که بایستی نتیجه خروجی را به کلاس همان کنترل Cast نماییم:
Button btn = FindViewById<Button>(Resource.Id.button1);
در اینجا جهت ساده شدن دستور Find، از ساختار Generic استفاده می‌شود. در این روش پس از دستور FindviewById، نوع کنترل را مشخص می‌نماییم و نتیجه خروجی را در متغیری از نوع var ذخیره نماییم که بطور اتوماتیک در سی شارپ نوع var به نوع آن شیء تبدیل میشود.
EditText همان Textbox خودمان می‌باشد. در Toolbox کنترلی به نام PlainText یک TextBox را به layout اضافه میکند؛ ولی در Activity یا همان برنامه نویسی، نام کلاس اصلی Textbox جهت برنامه نویسی، EditText می‌باشد:
FindViewById<EditText>(Resource.Id.txtname).Text = "";
که برای دسترسی مستقیم به Text و مقدار دهی به Property و یا Event‌ها، اینطور هم استفاده میشود.
  
ساخت برنامه‌های چندین فرمی

هر layout یک اکتیویتی مربوط به خودش را دارد. اگر بخواهیم بین فرم‌های مختلف حرکت کنیم، به ازای هر فرم، یک Activity کنترل کننده همان فرم را اضافه می‌کنیم. در یک Activity با ارسال سیگنالی به نام Intent که پارامتر اول آن، Activity مبدا یا This و پارامتر دوم آن، Activity مقصد می‌باشد. سپس به سیستم عامل اطلاع میدهیم که می‌خواهیم این سیگنال را ارسال کنیم و ارسال آن با StartActivity می‌باشد:
Intent _intent = new Intent(this, typeof(Activity2));
StartActivity(_intent);


ارسال پارامتر

جهت ارسال داده‌های معمولی bool,double,string,float,long,... و آرایه ای از آن، از دستور PutExtra استفاده می‌شود:
Intent _intent = new Intent(this, typeof(Activity2));
string Test="Vlaueone";

_intent.PutExtra("mainactivity", Test);
StartActivity(_intent);



گرفتن پارامتر

در Activity مقصد، اطلاعات مربوط به سیگنال ارسال شده، در مقادیر Global intent ذخیره می‌شود و بر اساس نوع داده ارسال شده، تابع GetExtra را می‌نویسیم:
string Getintent = Intent.GetStringExtra("mainactivity");



ارسال object از کلاس‌ها بین Activityها


در زامارین، object کلاس‌ها را به یک string استاندارد بصورت json تبدیل می‌کنیم و به این عملیات Serialization گفته میشود. این کار نیز توسط کتابخانه‌های مختلفی انجام می‌شود مانند NewtonSoft. سپس رشته json ایجاد شده را با تابع PutExtra، به همراه سیگنال Intent، به Activity دوم پاس میدهیم:
List<Product> products;
products = new List<Product>();
Product p = new Product
            {

                name = FindViewById<EditText>(Resource.Id.mainedittextname).Text,
                price = Convert.ToInt32(FindViewById<EditText>(Resource.Id.mainedittextprice).Text)
            };
products.Add(p);
Intent _intent = new Intent(this, typeof(Activity2));
_intent.PutExtra("products", JsonConvert.SerializeObject(products));
StartActivity(_intent);
در Activity دوم آن‌را توسط کتابخانه‌ی NewtonSoft، به کلاس اصلی DeSerialize میکنیم که عملیات آن با دستورات ذیل انجام  میشود:
var p = JsonConvert.DeserializeObject<List<Product>>(Intent.GetStringExtra("products"));
foreach (var item in p)
{
   Toast.MakeText(this, $" {item.name} , {item.price}",
                  ToastLength.Long).Show();
};
در کد بالا Toast را در حلقه می‌بینید که در واقع همانند alert در وب و یا همانند MessageBox در ویندوز فرم یا Wpf عمل میکند. در پارامتر اول به آن میگوییم که برای این اکتیویتی است. پارامتر دوم آن جهت نمایش مقدار و در پارامتر سوم، مدت نمایش طولانی برای آن در نظر گرفته شده است و با جایگزینی Long با Short، زمان نمایش آن کوتاه‌تر میشود و در آخر با show هم آن را نمایش میدهد.

در ادامه کلاس‌های مورد نظر را جهت تعریف در دایرکتوری Model ایجاد میکنیم. مثل کلاس Product که بصورت Public نیز می‌باشد. از منوی Tools -> Nuget Package Management -> Manage nuget Package for Solution  را انتخاب و سپس NewtonSoft را اضافه می‌کنیم.
از متد Finish برای اتمام کار و یا پایان Activity نیز استفاده میشود.
 
AleartDialog

AlertDialog زیر کلاس Dialog است که می‌تواند یک، دو و یا سه دکمه را نمایش دهد. اگر فقط می‌خواهید یک رشته را در این کادر محاوره ای نمایش دهید، از روش SetMessage استفاده کنید. قطعه کد زیر می‌تواند برای ایجاد یک AlertDialog ساده با دو دکمه حذف و لغو استفاده شود: 
//set alert for executing the task
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.SetTitle("Confirm delete");
alert.SetMessage("Lorem ipsum dolor sit amet, consectetuer adipiscing elit.");
alert.SetPositiveButton("Delete", (senderAlert, args) =>
            {
                Toast.MakeText(this, "Deleted!", ToastLength.Short).Show();
            });

alert.SetNegativeButton("Cancel", (senderAlert, args) =>
            {
                Toast.MakeText(this, "Cancelled!", ToastLength.Short).Show();
            });

Dialog dialog = alert.Create();
dialog.Show();
 
DialogFragment
AleartDialog فقط میتواند اطلاعات محدودی را نمایش دهد؛ مانند نمایش حداکثر یک یا دو Button,EditText به کاربر. در حالیکه در برنامه نیاز به دیالوگی با ظاهر سفارشی داریم تا بتوانیم ظاهر آن را تغییر دهیم.

Fragment

با استفاده از Fragment میتوانیم layout هایی از پیش طراحی شده را بسازیم و چند صفحه را بصورت layout با Activity پیاده سازی کنیم. در اینجا اگر layout دوم load بشود، در این حالت اگر نیاز به ورود داده‌های مورد نیاز از طریق layout اول یا اصلی را داشته باشد، برای کاربر خسته کننده می‌‌شود. از این رو بهتر است layout دوم کوچکی بر روی layout اول یا اصلی نمایش داده شود. از این رو میتوان بجای layout از DialogFragment استفاده کنیم.
کلاس  DialogFragment میتواند دیالوگی را در وسط صفحه نمایش دهد ولی view و ظاهر آن از یک layout از قبل ساخته شده load و نمایش داده میشود. این کار از طریق تکنیک Inflate انجام میشود.
در ادامه یک کلاس را از Dialog Fragment مشتق کرده و در تابع oncreate، ظاهر و یا layout از پیش طراحی شده را بصورت ذیل در حافظه load کرده و بعنوان نتیجه خروجی باز می‌گردانیم:
public class DialogFragmentActivity : DialogFragment
{
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            var dlg = inflater.Inflate(Resource.Layout.layoutDialog, container, false);
            return dlg;
        }
}
و در اکتیویتی اصلی برای فراخوانی کلاس فرگمنت از کد زیر استفاده میشود:
DialogFragmentActivity dlg = new DialogFragmentActivity ();
dlg.Show(this.FragmentManager, "Fragment");
پارامتر اول در واقع بعنوان fragment manager معاون اکتیویتی می‌باشد و در پارامتر دوم هم یک اسم دلخواه را برای آن انتخاب میکنیم. در متد باز نویسی شده On start نیز میتوان طول و عرض آن و رنگ پشت زمینه Fragment را تغییر داد که کد‌های آن را در زیر میتوانید مشاهده کنید:
public override void OnStart()
{
            base.OnStart();
            Dialog dialog = Dialog;
            if (dialog != null)
            {
                dialog.Window.SetLayout(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                dialog.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
            }
}
اگر برنامه را اجرا و فرگمنت را نیز اجرا کنید، می‌بینید که یک کادر، بصورت Header در بالای فرگمنت نمایان شده‌است که زیاد جالب نیست. برای حذف Header فرگمنت، از کد ذیل میتوانید استفاده کنید:
public override Dialog OnCreateDialog(Bundle savedInstanceState)
{
            Dialog NotTitle = base.OnCreateDialog(savedInstanceState);
            NotTitle.Window.RequestFeature(WindowFeatures.NoTitle);

            return NotTitle;
}
مطالب
Blazor 5x - قسمت 19 - کار با فرم‌ها - بخش 7 - نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server
تا قسمت قبل، روشی را که برای کار با EF-Core درنظر گرفتیم، روش متداول کار با آن، در برنامه‌های ASP.NET Core Web API بود؛ یعنی این روش با برنامه‌های مبتنی بر Blazor WASM که از دو قسمت مجزای Web API سمت سرور و Web Assembly سمت کلاینت تشکیل شده‌اند، به خوبی جواب می‌دهد؛ اما ... با Blazor Server یکپارچه که تمام قسمت‌های مدیریتی آن سمت سرور رخ می‌دهند، خیر! در این مطلب، دلایل این موضوع را به همراه ارائه راه‌حلی، بررسی خواهیم کرد.


طول عمر سرویس‌ها، در برنامه‌های Blazor Server متفاوت هستند

هنگامیکه با یک ASP.NET Core Web API متداول کار می‌کنیم، درخواست‌های HTTP رسیده، از میان‌افزارهای موجود رد شده و پردازش می‌شوند. اما هنگامیکه با Blazor Server کار می‌کنیم، به علت وجود یک اتصال دائم SignalR که عموما از نوع Web socket است، دیگر درخواست HTTP وجود ندارد. تمام رفت و برگشت‌های برنامه به سرور و پاسخ‌های دریافتی، از طریق Web socket منتقل می‌شوند و نه درخواست‌ها و پاسخ‌های متداول HTTP.
این روش پردازشی، اولین تاثیری را که بر روی رفتار یک برنامه می‌گذارد، تغییر طول عمر سرویس‌های آن است. برای مثال در برنامه‌های Web API، طول عمر درخواست‌ها، از نوع Scoped هستند و با شروع پردازش یک درخواست، سرویس‌های مورد نیاز وهله سازی شده و در پایان درخواست، رها می‌شوند.
این مساله در حین کار با EF-Core نیز بسیار مهم است؛ از این جهت که در برنامه‌های Web API نیز EF-Core و DbContext آن، به صورت سرویس‌هایی با طول عمر Scoped تعریف می‌شوند. برای مثال زمانیکه یک چنین تعریفی را در برنامه داریم:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
امضای واقعی متد AddDbContext مورد استفاده‌ی فوق، به صورت زیر است:
public static IServiceCollection AddDbContext<TContext>(
    [NotNullAttribute] this IServiceCollection serviceCollection, 
    [CanBeNullAttribute] Action<DbContextOptionsBuilder> optionsAction = null, 
    ServiceLifetime contextLifetime = ServiceLifetime.Scoped, 
    ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext;
همانطور که مشاهده می‌کنید، طول عمرهای پیش‌فرض تعریف شده‌ی در اینجا، از نوع Scoped هستند. یعنی زمانیکه سرویس‌های ApplicationDbContext را از طریق سیستم تزریق وابستگی‌های برنامه دریافت می‌کنیم، در ابتدای یک درخواست Web API، به صورت خودکار وهله سازی شده و در پایان درخواست رها می‌شوند. به این ترتیب به ازای هر درخواست رسیده، وهله‌ی متفاوتی از DbContex را دریافت می‌کنیم که با وهله‌ی استفاده شده‌ی در درخواست قبلی، یکی نیست.
اما زمانیکه مانند یک برنامه‌ی مبتنی بر Blazor Server، دیگر HTTP Requests متداولی را نداریم، چطور؟ در این حالت زمانیکه یک اتصال SignalR برقرار شد، وهله‌ای از DbContext که در اختیار برنامه‌ی Blazor Server قرار می‌گیرد، تا زمانیکه کاربر این اتصال را به نحوی قطع نکرده (مانند بستن کامل مرورگر و یا ریفرش صفحه)، ثابت باقی خواهد ماند. یعنی به ازای هر اتصال SignalR، طول عمر ServiceLifetime.Scoped پیش‌فرض تعریف شده، همانند یک وهله‌ی با طول عمر Singleton عمل می‌کند. در این حالت تمام صفحات و کامپوننت‌های یک برنامه‌ی Blazor Server، از یک تک وهله‌ی مشخص DbContext که در ابتدای کار دریافت کرده‌اند، کار می‌کنند و از آنجائیکه DbContext به صورت thread-safe کار نمی‌کند، این تک وهله مشکلات زیادی را ایجاد خواهد کرد که یک نمونه از آن‌را در عمل، در پایان قسمت قبل مشاهده کردید:
«اگر برنامه را اجرا کرده و سعی در حذف یک ردیف کنیم، به خطای زیر می‌رسیم و یا حتی اگر کاربر شروع کند به کلیک کردن سریع در قسمت‌های مختلف برنامه، باز هم این خطا مشاهده می‌شود:
 An exception occurred while iterating over the results of a query for context type 'BlazorServer.DataAccess.ApplicationDbContext'.
System.InvalidOperationException: A second operation was started on this context before a previous operation completed.
This is usually caused by different threads concurrently using the same instance of DbContext.
For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
عنوان می‌کند که متد OnConfirmDeleteRoomClicked، بر روی ترد دیگری نسبت به ترد اولیه‌ای که DbContext بر روی آن ایجاد شده، در حال اجرا است و چون DbContext برای یک چنین سناریوهایی، thread-safe نیست، اجازه‌ی استفاده‌ی از آن‌را نمی‌دهد.»
هر درخواست Web API نیز بر روی یک ترد جداگانه اجرا می‌شود؛ اما چون ابتدا و انتهای درخواست‌ها مشخص است، طول عمر Scoped، در ابتدای درخواست شروع شده و در پایان آن رها سازی می‌شود. به همین جهت استثنائی را که در اینجا مشاهده می‌کنید، در برنامه‌های Web API شاید هیچگاه مشاهده نشود.


معرفی DbContextFactory در EF Core 5x

همواره باید طول عمر DbContext را تا جای ممکن، کوتاه نگه داشت. مشکل فعلی ما، Singleton رفتار کردن DbContext‌ها (داشتن طول عمر طولانی) در برنامه‌های Blazor Server هستند. یک چنین رفتاری را شاید در برنامه‌های دسکتاپ هم پیشتر مشاهده کرده باشید. برای مثال در برنامه‌های دسکتاپ WPF، تا زمانیکه یک فرم باز است، Context ایجاد شده‌ی در آن هم برقرار است و Dispose نمی‌شود. در یک چنین حالت‌هایی، عموما Context را در زمان نیاز، ایجاد کرده و پس از پایان آن کار کوتاه، Context را رها می‌کنند. به همین جهت نیاز به DbContext Factory ای وجود دارد که بتواند یک چنین پیاده سازی‌هایی را میسر کند و خوشبختانه از زمان EF Core 5x، یک چنین امکانی خصوصا برای برنامه‌های Blazor Server تحت عنوان DbContextFactory ارائه شده‌است که به عنوان راه حل استاندارد دسترسی به DbContext در اینگونه برنامه‌ها مورد استفاده قرار می‌گیرد.
برای کار با DbContextFactory، اینبار در فایل BlazorServer.App\Startup.cs، بجای استفاده از services.AddDbContext، از متد AddDbContextFactory استفاده می‌شود:
public void ConfigureServices(IServiceCollection services)
{
    var connectionString = Configuration.GetConnectionString("DefaultConnection");
    //services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
    services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
سپس باید دقت داشت که روش استفاده‌ی از آن، نسبت به کار مستقیم با ApplicationDbContext، کاملا متفاوت است. هدف از DbContextFactory، ساخت دستی Context در زمان نیاز و سپس Dispose صریح آن است. بنابراین طول عمر Context دریافت شده‌ی توسط آن باید توسط برنامه نویس مدیریت شود و به صورت خودکار توسط IoC Container برنامه مدیریت نخواهد شد. در این حالت دو روش استفاده‌ی از آن در کامپوننت‌های برنامه‌های Blazor Server، پیشنهاد می‌شود.


روش اول کار با DbContextFactory در کامپوننت‌های Blazor Server : وهله سازی از نو، به ازای هر متد

در این روش پس از ثبت AddDbContextFactory در فایل Startup برنامه مانند مثال فوق، ابتدا سرویس IDbContextFactory که به ApplicationDbContext اشاره می‌کند به ابتدای کامپوننت تزریق می‌شود:
@inject IDbContextFactory<ApplicationDbContext> DbFactory
سپس در هر جائی که نیاز به وهله‌ای از ApplicationDbContext است، آن‌را به صورت دستی وهله سازی کرده و همانجا هم Dispose می‌کنیم:
private async Task DeleteImageAsync()
{
    using var context = DbFactory.CreateDbContext();

    var image = await context.HotelRoomImages.FindAsync(1);

   // ...
}
در اینجا یکی متدهای یک کامپوننت فرضی را مشاهده می‌کند که از DbFactory تزریق شده استفاده کرد و سپس با استفاده از متد ()CreateDbContext، وهله‌ی جدیدی از ApplicationDbContext را ایجاد می‌کند. همچنین در همان سطر، وجود عبارت using نیز مشاهده می‌شود. یعنی در پایان کار این متد، context ایجاد شده حتما Dispose شده و طول عمر کوتاهی خواهد داشت.


روش دوم کار با DbContextFactory در کامپوننت‌های Blazor Server : یکبار وهله سازی Context به ازای هر کامپوننت

در این روش می‌توان طول عمر Context را معادل طول عمر کامپوننت تعریف کرد که مزیت استفاده‌ی از Change tracking موجود در EF-Core را به همراه خواهد داشت. در این حالت کامپوننت‌های Blazor Server، شبیه به فرم‌های برنامه‌های دسکتاپ عمل می‌کنند:
@implements IDisposable
@inject IDbContextFactory<ApplicationDbContext> DbFactory


@code
{
   private ApplicationDbContext Context;

   protected override async Task OnInitializedAsync()
   {
       Context = DbFactory.CreateDbContext();
       await base.OnInitializedAsync();
   }

   private async Task DeleteImageAsync()
   {
       var image = await Context.HotelRoomImages.FindAsync(1);
       // ...
   }

   public void Dispose()
   {
     Context.Dispose();
   }
}
- در اینجا همانند روش اول، کار با تزریق IDbContextFactory شروع می‌شود
-  اما بجای اینکه به ازای هر متد، کار فراخوانی DbFactory.CreateDbContext صورت گیرد، یکبار در آغاز کار کامپوننت و در روال رویدادگردان OnInitializedAsync، کار وهله سازی Context کامپوننت انجام شده و از این تک Context در تمام متدهای کامپوننت استفاده خواهد شد.
- در این حالت کار Dispose خودکار این Context به متد Dispose نهایی کل کامپوننت واگذار شده‌است. برای اینکه این متد فراخوانی شود، نیاز است در ابتدای تعاریف کامپوننت، از دایرکتیو implements IDisposable@ استفاده کرد.


سؤال: اگر سرویسی از ApplicationDbContext تزریق شده‌ی در سازنده‌ی خود استفاده می‌کند، چکار باید کرد؟

برای نمونه سرویس‌های از پیش تعریف شده‌ی ASP.NET Core Identity، در سازنده‌ی خود از ApplicationDbContext استفاده می‌کنند و نه از IDbContextFactory. در این حالت برای تامین ApplicationDbContext‌های تزریق شده، فقط کافی است از روش زیر استفاده کنیم:
services.AddScoped<ApplicationDbContext>(serviceProvider =>
     serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
در این حالت به ازای هر Scope تعریف شده‌ی در برنامه، جهت دسترسی به ApplicationDbContext از طریق سیستم تزریق وابستگی‌ها، کار فراخوانی DbFactory.CreateDbContext به صورت خودکار انجام خواهد شد.


سؤال: روش پیاده سازی سرویس‌های یک برنامه Blazor Server به چه صورتی باید تغییر کند؟

تا اینجا روش‌هایی که برای استفاده از IDbContextFactory معرفی شدند (که روش‌های رسمی و توصیه شده‌ی اینکار نیز هستند)، فرض را بر این گذاشته‌اند که ما قرار است تمام منطق تجاری کار با بانک اطلاعاتی را داخل همان متدهای کامپوننت‌ها انجام دهیم (این روش برنامه نویسی، بسیار مورد علاقه‌ی مایکروسافت است و در تمام مثال‌های رسمی آن به صورت ضمنی توصیه می‌شود!). اما اگر همانند مثالی که تاکنون در این سری بررسی کردیم، نخواهیم اینکار را انجام دهیم و علاقمند باشیم تا این منطق تجاری را به سرویس‌های مجزایی، با مسئولیت‌های مشخصی انتقال دهیم، روش استفاده‌ی از IDbContextFactory چگونه خواهد بود؟
در این حالت از ترکیب روش دوم مطرح شده‌ی استفاده از IDbContextFactory که به همراه مزیت دسترسی کامل به Change Tracking توکار EF-Core و پیاده سازی الگوی واحد کار است و وهله سازی خودکار ApplicationDbContext که معرفی شد، استفاده خواهیم کرد؛ به این صورت:
الف) تمام سرویس‌های EF-Core یک برنامه‌ی Blazor Server باید اینترفیس IDisposable را پیاده سازی کنند.
این مورد برای سرویس‌های پروژه‌های Web API، ضروری نیست؛ چون طول عمر Context آن‌ها توسط خود IoC Container مدیریت می‌شود؛ اما در برنامه‌های Blazor Server، مطابق توضیحاتی که ارائه شد، خودمان باید این طول عمر را مدیریت کنیم.
بنابراین به پروژه‌ی سرویس‌های برنامه مراجعه کرده و هر سرویسی که ApplicationDbContext تزریق شده‌ای را در سازنده‌ی خود می‌پذیرد، یافته و تعریف اینترفیس آن‌را به صورت زیر تغییر می‌دهیم:
public interface IHotelRoomService : IDisposable
{
   // ...
}

public interface IHotelRoomImageService : IDisposable
{
   // ...
}
سپس باید اینترفیس‌های IDisposable را پیاده سازی کرد که روش مورد پذیرش code analyzer‌ها در این زمینه، رعایت الگوی زیر، دقیقا به همین شکل است و باید از دو متد تشکیل شود:
    public class HotelRoomService : IHotelRoomService
    {
        private bool _isDisposed;

        // ...

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                try
                {
                    if (disposing)
                    {
                        _dbContext.Dispose();
                    }
                }
                finally
                {
                    _isDisposed = true;
                }
            }
        }
    }
این الگو را به همین شکل برای سرویس HotelRoomImageService نیز پیاده سازی می‌کنیم.


ب) Dispose دستی تمام سرویس‌ها، در کامپوننت‌های مرتبط
در ادامه تمام کامپوننت‌هایی را که از سرویس‌های فوق استفاده می‌کنند یافته و ابتدا دایرکتیو implements IDisposable@ را به ابتدای آن‌ها اضافه می‌کنیم. سپس متد Dispose آن‌ها را جهت فراخوانی متد Dispose سرویس‌های فوق، تکمیل خواهیم کرد:
بنابراین ابتدا به فایل BlazorServer\BlazorServer.App\Pages\HotelRoom\HotelRoomUpsert.razor مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
@page "/hotel-room/create"
@page "/hotel-room/edit/{Id:int}"

@implements IDisposable
// ...


@code
{
    // ...

    public void Dispose()
    {
        HotelRoomImageService.Dispose();
        HotelRoomService.Dispose();
    }
}
و همچنین به کامپوننت BlazorServer\BlazorServer.App\Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و آن‌را به صورت زیر جهت Dispose دستی سرویس‌ها، تکمیل می‌کنیم:
@page "/hotel-room"

@implements IDisposable
// ...


@code
{
    // ...

    public void Dispose()
    {
        HotelRoomService.Dispose();
    }
}


مشکل! اینبار خطای dispose شدن context را دریافت می‌کنیم!

System.ObjectDisposedException: Cannot access a disposed context instance.
A common cause of this error is disposing a context instance that was resolved from dependency injection and then
later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose'
on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context instances.
Object name: 'ApplicationDbContext'.
هم برنامه‌های Blazor WASM و هم برنامه‌های Blazor Server از مفهوم طول عمرهای تنظیم شده‌ی سرویس‌ها پشتیبانی نمی‌کنند! در هر دوی این‌ها اگر سرویسی را با طول عمر Scoped تنظیم کردیم، رفتار آن همانند سرویس‌های Singleton خواهد بود. تنها زمانی رفتارهای Scoped و یا Transient پشتیبانی می‌شوند که درخواست HTTP ای رخ داده باشد که این مورد خارج است از طول عمر یک برنامه‌ی Blazor WASM و همچنین اتصال SignalR برنامه‌های Blazor Server. فقط قسمت‌هایی از برنامه‌ی Blazor Server که با مدل قبلی Razor pages طراحی شده‌اند، چون سبب شروع یک درخواست HTTP معمولی می‌شوند، همانند برنامه‌های متداول ASP.NET Core رفتار می‌کنند و در این حالت طول عمرهای غیر Singleton مفهوم پیدا می‌کنند.

مشکلی که در اینجا رخ داده این است که سرویس‌هایی را داریم با طول عمر به ظاهر Scoped که یکی از وابستگی‌های آن‌ها را به صورت دستی Dispose کرده‌ایم. چون طول عمر Scoped در اینجا وجود ندارد و طول عمرها در اصل Singleton هستند، هربار که سرویس مدنظر مجددا درخواست شود، همان وهله‌ی ابتدایی که اکنون یکی از وابستگی‌های آن Dispose شده، در اختیار برنامه قرار می‌گیرد.
پس از این تغییرات، اولین باری که برنامه را اجرا می‌کنیم، لیست اتاق‌ها به خوبی نمایش داده می‌شوند و مشکلی نیست. بعد در همین حال و در همین صفحه، اگر بر روی دکمه‌ی افزودن یک اتاق جدید کلیک کنیم، اتفاقی که رخ می‌دهد، فراخوانی متد Dispose کامپوننت لیست اتاق‌ها است (بر روی آن یک break-point قرار دهید). بنابراین متد Dispose یک کامپوننت، با هدایت به یک مسیر دیگر، به صورت خودکار فراخوانی می‌شود. در این حالت Context برنامه Dispose شده و در کامپوننت ثبت یک اتاق جدید دیگر، در دسترس نخواهد بود؛ چون IHotelRoomService مورد استفاده مجددا وهله سازی نمی‌شود و از همان وهله‌ای که بار اول ایجاد شده، استفاده خواهد شد.
 
بنابراین سؤال اینجا است که چگونه می‌توان سیستم تزریق وابستگی‌ها را وادار کرد تا تمام سرویس‌های تزریق شده‌ی به سازنده‌ها‌ی سرویس‌های HotelRoomService و  HotelRoomImageService را مجددا وهله سازی کند و سعی نکند از همان وهله‌های قبلی استفاده کند؟

پاسخ: یک روش این است که IHotelRoomImageService را خودمان به ازای هر کامپوننت به صورت دستی در روال رویدادگردان OnInitializedAsync وهله سازی کرده و DbFactory.CreateDbContext جدیدی را مستقیما به سازنده‌ی آن ارسال کنیم. در این حالت مطمئن خواهیم شد که این وهله، جای دیگری به اشتراک گذاشته نمی‌شود:
@code
{
   private IHotelRoomImageService HotelRoomImageService;

   protected override async Task OnInitializedAsync()
   {
       HotelRoomImageService =  new HotelRoomImageService(DbFactory.CreateDbContext(), mapper);
       await base.OnInitializedAsync();
   }

   private async Task DeleteImageAsync()
   {
       await HotelRoomImageService.DeleteAsync(1);
       // ...
   }

   public void Dispose()
   {
     HotelRoomImageService.Dispose();
   }
}
هرچند این روش کار می‌کند، اما در زمان استفاده از IoC Container‌ها قرار نیست کار انجام new‌ها را خودمان به صورت دستی انجام دهیم و بهتر است مدیریت این مساله به آن‌ها واگذار شود.


وادار کردن Blazor Server به وهله سازی مجدد سرویس‌های کامپوننت‌ها

بنابراین مشکل ما Singleton رفتار کردن سرویس‌ها، در برنامه‌های Blazor است. برای مثال در برنامه‌های Blazor Server، تا زمانیکه اتصال SignalR برنامه برقرار است (مرورگر بسته نشده، برگه‌ی جاری بسته نشده و یا کاربر صفحه را ریفرش نکرده)، هیچ سرویسی دوباره وهله سازی نمی‌شود.
برای رفع این مشکل، امکان Scoped رفتار کردن سرویس‌های یک کامپوننت نیز در نظر گرفته شده‌اند. برای نمونه کدهای کامپوننت HotelRoomList.razor را به صورت زیر تغییر می‌دهیم:
@page "/hotel-room"

@*@implements IDisposable*@
@*@inject IHotelRoomService HotelRoomService*@
@inherits OwningComponentBase<IHotelRoomService>
با استفاده از دایرکتیو جدید inherits OwningComponentBase@ می‌توان میدان دید یک سرویس را به طول عمر کامپوننت جاری محدود کرد. هربار که این کامپوننت نمایش داده می‌شود، وهله سازی شده و هربار که به کامپوننت دیگری هدایت می‌شویم، به صورت خودکار سرویس مورد استفاده را Dispose می‌کند. بنابراین در اینجا دیگر نیازی به ذکر دایرکتیو implements IDisposable@ نیست.

چند نکته:
- فقط یکبار به ازای هر کامپوننت می‌توان از دایرکتیو inherits استفاده کرد.
- زمانیکه طول عمر سرویسی را توسط OwningComponentBase مدیریت می‌کنیم، در حقیقت یک کلاس پایه را برای آن کامپوننت درنظر گرفته‌ایم که به همراه یک خاصیت عمومی ویژه، به نام Service و از نوع سرویس مدنظر ما است. در این حالت یا می‌توان از خاصیت Service به صورت مستقیم استفاده کرد و یا می‌توان به صورت زیر، همان کدهای قبلی را داشت و هربار که نیازی به HotelRoomService بود، آن‌را به خاصیت عمومی Service هدایت کرد:
@code
{
   private IHotelRoomService HotelRoomService => Service;
- اگر نیاز به بیش از یک سرویس وجود داشت، چون نمی‌توان بیش از یک inherits را تعریف کرد، می‌توان از نمونه‌ی غیرجنریک OwningComponentBase استفاده کرد:
@page "/preferences"
@using Microsoft.Extensions.DependencyInjection
@inherits OwningComponentBase


@code {
    private IHotelRoomService HotelRoomService { get; set; }
    private IHotelRoomImageService HotelRoomImageService { get; set; }

    protected override void OnInitialized()
    {
        HotelRoomService = ScopedServices.GetRequiredService<IHotelRoomService>();
        HotelRoomImageService = ScopedServices.GetRequiredService<IHotelRoomImageService>();
    }
}
در این حالت کلاس پایه‌ی OwningComponentBase، به همراه خاصیت جدید ScopedServices است که با فراخوانی متد GetRequiredService در روال رویدادگردان OnInitialized بر روی آن، سبب وهله سازی Scoped سرویس مدنظر خواهد شد. نمونه‌ی جنریک آن، تمام این موارد را در پشت صحنه انجام می‌دهد و کار کردن با آن ساده‌تر و خلاصه‌تر است.


خلاصه‌ی بحث جاری در مورد روش مدیریت DbContext برنامه‌های Blazor Server:

- بجای services.AddDbContext متداول، باید از AddDbContextFactory استفاده کرد:
services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
services.AddScoped<ApplicationDbContext>(serviceProvider =>
        serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
- تمام سرویس‌هایی که از ApplicationDbContext استفاده می‌کنند، باید به همراه پیاده سازی Dispose آن نیز باشند؛ چون Scope یک سرویس، معادل طول عمر اتصال SignalR برنامه است و مدام وهله سازی نمی‌شود. در این حالت باید وهله سازی و Dispose آن‌را دستی مدیریت کرد.
- کامپوننت‌های برنامه، سرویس‌هایی را که باید Scoped عمل کنند، دیگر نباید از طریق تزریق مستقیم آن‌ها دریافت کنند؛ چون در این حالت همواره به همان وهله‌ای که در ابتدای کار ایجاد شده، می‌رسیم:
@inject IHotelRoomService HotelRoomService
این دریافت باید با استفاده از کلاس پایه OwningComponentBase صورت گیرد:
@inherits OwningComponentBase<IHotelRoomService>
تا عملیات فراخوانی خودکار ScopedServices.GetRequiredService (دریافت وهله‌ی جدید Scoped) و همچنین Dispose خودکار آن‌ها را به ازای هر کامپوننت مجزا، مدیریت کند.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-19.zip
مطالب
babel چیست؟ lebab چیست؟
قطعا به عنوان برنامه نویس JavaScript کم و بیش با ecmaScript 6 آشنایی دارید.
با وجود ویژگی‌های منحصر به فردی که دارد شاید تنها دلیلی که برخی از برنامه نویسان هنوز تصمیم به عدم استفاده از این زبان را دارند، مرورگرهایی میباشند که هنوز از es6 پشتیبانی نمیکنند: es6 compatibility table 
اما راهکاری مناسب، برای اینکه بتوان هم از es6 استفاده کرد و هم کاربران را مجبور به استفاده‌ی از مرورگر‌های مدرن نکنیم نیز وجود دارد:به صورت مستقیم میتوان با استفاده از Babel، کد‌های نوشته شده‌ی با es6 را کامپایل و تبدیل به es5 کنیم. برای اینکار معمولا از gulp استفاده میکنیم. 
gulp در وبسایت رسمی آن، خودش را اینچنین تعریف کرده است : automate and enhance your workflow
حال کافیست با چند خط کد es6 شروع به کار کنیم. بنده معمولا از visual studio code استفاده میکنم.
class firstCLs
{
    doFirst(str)
    {
        console.log(str);
    }
}

class secondCls extends firstCLs
{
    doSecond(str)
    {
        super.doFirst(str);
    }
}

let str = 'string';
new secondCls().doSecond(`this is some ${str}`);
فکر میکنم کد‌های فوق احتیاج به توضیح بیشتری نداشته باشند. اکنون فرض کنید نام فایل آن code.js است و در پوشه‌ی src قرار دارد. همانطور که پیشتر عرض کردم، برای تبدیل آن به کدی از نوع es5 از gulp استفاده می‌کنیم.
ابتدا از طریق خط فرمان، خود gulp را نصب میکنیم. 
npm install -g gulp
بعد بر روی پوشه‌ی root پروژه‌تان رفته و با استفاده از خط فرمان، npm init را ارسال کنید تا package.json برای شما ساخته شود.
برای نصب gulp در پروژه‌ی local خود نیز این فرمان را ارسال کنید:
npm install gulp --save-dev
با استفاده از این فرمان، gulp را در مسیر جاری پروژه‌تان و در پوشه‌ی node-modules نصب کرده و همچنین فرمان --save-dev نیز آن را به وابستگی‌های پروژه اضافه میکند.
حال احتیاج به نصب gulp-babel داریم که با استفاده از خط فرمان، خود آن را نصب مینماییم:
npm install --save-dev gulp-babel babel-preset-es2015
حال در مسیر اصلی پروژه، فایل gulpfile.js را ساخته و کدهای زیر را مینویسیم:
const gulp = require('gulp');
const babel = require('gulp-babel');
 
gulp.task('default', () => {
return gulp.src('src/code.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('dist'));
});
برای توضیح کد‌های بالا باید عرض کنم در قسمت gulp.task، عملیاتی را که لازم میدانیم gulp برای ما انجام دهد، شرح میدهیم.
gulp.src مسیر فایلی را که تصمیم داریم کامپایل شده و به es5 تبدیل شود، معین می‌کند ( لازم به ذکر است که میتوان بطور مثال همه‌ی اسکریپت‌های درون یک پوشه را انتخاب کرد که توضیح آن در این مقاله نمیگنجد).
بعد در قسمت اولین pipe فوق است که میخواهیم تبدیلی را به es2015 داشته باشیم و در آخرین pipe نیز با استفاده از متد dest، آدرس مسیری را که میخواهیم آن فایل کامپایل شده در آن قرار بگیرد، مینویسیم. بطور مثال من پوشه‌ی dist را به عنوان آدرس قرار داده‌ام.
مراحل انجام شده را save نمایید. خط فرمان خود را باز کرده و دستور gulp را type نمایید. بعد از پایان یافتن عملیات، به پوشه‌ی dist رجوع کنید. فایل مورد نظر مشاهد میشود.

اما حال فرض کنید پروژه ای در اختیار داشته و کد‌ها از نوع es5 میباشند و تصمیم به تبدیل آن‌ها به es6 داشته باشید.
برای اینکار از lebab (که دقیقا عکس نوشتاری bable میباشد) استفاده میکنیم.
ابتدا از طریق خط فرمان خود آن را نصب مینماییم
npm install -g lebab
به صورت مستقیم میتوانیم هر فایلی را که به صورت استاندارد نوشته شده است، کامپایل کرده و تبدیل به es6 نماییم.
کد‌های زیر را در نظر بگیرید
'use strict';

// Let/const
var name = 'Bob', time = 'yesterday';
time = 'today';

// Template string
console.log('Hello ' + name + ', how are you ' + time + '?');

var bob = {
  // Object shorthand
  name: name,
  // Object method
  sayMyName: function () {
    console.log(this.name);
  }
};

// Classes
var SkinnedMesh = function SkinnedMesh() {
};

SkinnedMesh.prototype.update = function (camera) {
  camera = camera || createCamera();
  this.camera = camera;
};

Object.defineProperty(SkinnedMesh.prototype, 'name', {
  set: function (geometry) {
    this.geometry = geometry;
  },
  get: function () {
    return this.geometry;
  }
});

// Commonjs
var lebab = require('lebab');
module.exports = SkinnedMesh;

// Arrow functions
var render = function () {
  // ...
  requestAnimationFrame(render);
};
فرض کنید آن را با نام es5.js در مسیر اصلی پروژه ذخیره کرده‌ایم. با استفاده از خط فرمان خود آن را تبدیل به es6 خواهیم کرد؛ بدین شکل:
lebab es5.js -o es6.js
فایل مورد نظر ما به نام es6.js ذخیره خواهد شد و بدین صورت کامپایل خواهد شد
const name = 'Bob';
let time = 'yesterday';
time = 'today';

// Template string
console.log(`Hello ${name}, how are you ${time}?`);

const bob = {
  // Object shorthand
  name,
  // Object method
  sayMyName() {
    console.log(this.name);
  }
};

class SkinnedMesh {
  update(camera=createCamera()) {
    this.camera = camera;
  }

  set name(geometry) {
    this.geometry = geometry;
  }

  get name() {
    return this.geometry;
  }
}

import lebab from 'lebab';
export default SkinnedMesh;

// Arrow functions
const render = () => {
  // ...
  requestAnimationFrame(render);
};
تبدیل es5 به es6 انجام شد؛ اما باید خدمتتان عرض کنم آنچنان هم نباید انتظار داشت که بعد از این تبدیل و فشار دادن دکمه‌ی F5، پروژه‌ی شما بدون هیچ خطایی اجرا شود (البته در صورتیکه هنوز در اوایل پروژه هستید شاید اینگونه شود) اما کمی refactoring برای کد‌های کامپایل شده را به es 6، در خاطر داشته باشید.