نظرات مطالب
کنترل شرایط تاثیرگذار بر روی یک نقش در ASP.NET MVC
با تشکر  ، ایده‌ی خوبی است . بنده خودم برای موارد این چنینی متد چک کردن را داخل متد اصلی (سرویس) قرار می‌دهم برای نمونه مثال شما رو به صورت :
Public editStatus Edit(int id){
if (IsPassed(id)) return youCantEditThisDoc;
// do edit
}
بنده خودم این روش را ترجیح میدهم به این دلیل که شاید برنامه نویسی که قصد پیاده سازی لایه UI را دارد شاید از این نحوه کارکرد اطلاع نداشته باشد و تا حدودی متد‌ها کپسوله میشود .
مطالب
ایجاد اعتبار سنجی های شرطی با Foolproof
ابتدا کلاس زیر را در نظر بگیرید:
public class UserVM  
 {
        public string Name { get; set; }
        public bool  Gender { get; set; }
        public string Soldier { get; set; }
    }
قصد داریم یک سری اعتبار سنجی را بر روی خصوصیات کلاس فوق ایجاد کنیم. می‌خواهیم اگر کاربر جنسیت مرد را انتخاب کرد، حتما مقداری برای فیلد محل خدمت خود که در این کلاس Soldier می‌باشد، انتخاب کند. شاید انتخاب اول برای انجام چنین کاری، کنترل کردن آن در سمت کاربر با استفاده از جاوا اسکریپت باشد که می‌بایست یک رویداد را برای چک باکس جنسیت تعریف کنیم و بر اساس اینکه مرد انتخاب شده یا زن، ادامه کار را انجام دهیم.

روش اول: نوشتن یک کلاس سفارشی برای اعتبار سنجی کلاس فوق
public class SoldierValidation : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        UserVM app = value as UserVM ;
        if (app.Gender && app.Soldier.Length==0)
        {
            ErrorMessage = "لطفا محل خدمت را وارد نمایید";

            return false;
        }
        return true;
    }
}
و سپس اعمال به کلاس مورد نظر همانند زیر :
[SoldierValidation ]
   public class UserVM
    {
        public string Name { get; set; }
        public bool  Gender { get; set; }       
        public string Soldier { get; set; }
    }
تا اینجای کار، اگر کاربر از DropDown و یا RadioButton، آقا را انتخاب کرده باشد و View مورد نظر را برای Update و یا Insert ارسال کند، با خطای «لطفا محل خدمت را وارد نمایید» مواجه خواهد شد. تا به اینجا به مقصود مورد نظرمان رسیدیم.

روش دوم
لازم نیست چرخ رو دوباره اختراع کنید (البته در بعضی مواقع لازم است)
استفاده از MVC Foolproof:
ّFoolprof یک سری Annotation هایی را در اختیار شما قرار  می‌دهد که با استفاده از آنها می‌توانید اعتبار سنجی‌های شرطی را انجام دهید؛ دقیقا همانند کاری که در بالا برای آن یک Validation سفارشی نوشتیم. البته Foolproof فقط به این مورد ختم نمی‌شود. در ادامه با چند مورد از آنها آشنا خواهیم شد.

ابتدا از طریق NuGet اقدام به نصب Foolproof نمایید:
PM> Install-Package foolproof
سپس اینبار همان مثال خود را با FoolPfoof انجام می‌دهیم:
   public class UserVM
    {
        public string Name { get; set; }
        public bool  Gender { get; set; }
        [RequiredIfTrue("Gender ")]
        public string Soldier { get; set; }
    }
 با استفاده از RequiredIfTrue دقیقا به همان مقصود خواهیم رسید که از ورودی، اسم فیلدی را می‌گیرد که می‌خواهیم آن را چک کنیم.

حال بپردازیم به چندین Annotation دیگر که در Foolproof وجود دارند:
GreatThan: همانطور که از نام آن پیداست، برای موقعیکه می‌خواهیم این فیلد بزرگتر از فیلد مورد نظرمان باشد:
public class EventViewModel
{
    public string Name { get; set; }
    public DateTime Start { get; set; }

    [Required]
    [GreaterThan("Start")]
    public DateTime End { get; set; }
}
جهت آشنایی بیشتر، در ادامه فقط لیست Annotation‌های موجود در این پکیج قرار داده شده است .
[Is]
[EqualTo]
[NotEqualTo]
[GreaterThan]
[LessThan]
[GreaterThanOrEqualTo]
[LessThanOrEqualTo]
و
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
مطالب دوره‌ها
خطا ها و مدیریت خطا (Exception Handling)
مدیریت خطا در #F شبیه به الگوی try catch finally در #C است. برای تعریف خطا از کلمه کلیدی exception استفاده می‌کنیم و یک نام رو به اون اختصاص می‌دهیم و می‌تونیم به صورت اختیاری یک نوع داده رو هم برای این خطا با استفاده از کلمه کلیدی of تعیین کنیم.
exception myError of int
با استفاده از دستور raise می‌تونیم یک exception رو پرتاب کنیم.(به دلیل اینکه در دات نت از دستور throw به معنی پرتاب کردن استفاده می‌کنیم این جا نیز از همین لغت استفاده کردم کما اینکه در #F دستور raise جایگزین throw شده است). البته در جاهایی که قصد ما از پرتاب exception فقط متوقف کردن عملیات و نمایش یک خطا است می‌تونیم از دستور failwith به همراه یک پیغام نیز استفاده کنیم.(یک نمونه از آن را در فصل‌های قبلی مشاهده کردید)
ساختار کلی  try catch finally در #F به صورت زیر است.(تنها تفاوت در کلمه with به جای catch است)
try
// try code here
with
//catch statement here
یا به صورت
try
// try code here
finally
//finally statement here
*نکته مهم: در #F شما اجازه استفاده از finally رو به همراه with ندارید.به همین دلیل من این ساختارو به دو صورت بالا نوشتم.

یک مثال از try with:
exception WrongSecond of int//یک exception تعریف می‌کنیم

let primes =
[ 2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59 ]

// یک تابع برای تست اینکه آیا ثانیه الان در لیست prime وجود دارد یا نه
let testSecond() =
try
let currentSecond = System.DateTime.Now.Second in

// شرط برای اینکه مشخص شود که ثانیه در لیست است یا خیر
if List.exists (fun x -> x = currentSecond) primes then

// اگر بود یک خطا تولید می‌شود
failwith "A prime second"

else

// اگر نیود یک استثنا از نوع wrongSecond پرتاب میشود
raise (WrongSecond currentSecond)

with
// catch کردن استثناها

WrongSecond x ->
printf "The current was %i, which is not prime" x

در کد با در هر خط توضیحات لازم داده شده است. نکته قابل ذکر این است که در #C زمانی که قصد داشته باشیم یک استثنا جدید ایجاد کنیم باید کلاسی جدیدی که از کلاس System.Exception ارث برده باشد(یا هر کلاس دیگری که خود از این System.Exception ارث برده است) ایجاد کنیم و کد‌های مورد نظر رو در اون قرار بدیم. ولی در اینجا (در قسمتی که رنگ آن متفاوت است) به راحتی توانستیم یک استثنا جدید بر اساس نیاز بسازیم.

یک مثال از try finally :
// تابعی برای نوشتن فایل
let writeToFile() =
//ابتدا فایل به صورت متنی ساخته می‌شود
let file = System.IO.File.CreateText("test.txt")
try
// متن مورد نظر در فایل نوشته می‌شود
file.WriteLine("Hello F# users")
finally
//فایل مورد نظر بسته می‌شود.این دستور حتی اگر در هنگام نوشتن فایل استثنا هم رخ بدهد اجرا خواهد شد
file.Dispose()
عملکرد finally در #F دقیقا مشابه با عملکرد finally در #C است. یعنی دستورات بلوک finally همواره (چه استثنا رخ بدهد و چه رخ ندهد) اجرا خواهد شد.

*توجه :
برنامه نویسانی که قبلا با OCaml کدنویسی کرده اند هنگام برنامه نویسی #F از raise کردن‌های زیاد و بی مورد استثناها خودداری کنند. به دلیل نوع معماری CLR پرتاب کردن استثنا و مدیریت آن کمی هزینه بر است (بیشتر از زبان Ocaml). البته این مسئله در زبان‌های تحت دات نت نیز مطرح است کما اینکه در #C نیز مدیریت استثناها رو در بالاترین لایه انجام می‌دهیم و از catch کردن بی مورد استثنائات در لایه‌های زیرین خودداری می‌کنیم.

یک مثال از الگوی Matching در try with

let getNumber msg =
    printf msg;
    try
        int32(System.Console.ReadLine())
    with
        | :? System.FormatException -> -1
        | :? System.OverflowException -> System.Int32.MinValue
        | :? System.ArgumentNullException -> 0

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

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

گام اول:
فایل‌های مورد نظر را بعد از صدا زدن کتابخانه‌ی جی کوئری صدا بزنید.
<link type="text/css" href="css/RowAdder.css" rel="stylesheet" />
    <script src="js/RowAdder.js" type="text/javascript"></script>


گام دوم :
 در تکه کدهای html، کدی را که قرار است در هر سطر تکرار شود، داخل یک div قرار داده و نامی مثل row-sample را برای آن قرار دهید (فعلا حتما این نام باشد)، بعدها پلاگین، کدهای داخل این تگ div را به عنوان هر سطر خواهد شناخت:
<div id="row-sample">
    <form style="margin: 0; padding: 0;">
        Name:<input type="text"/>
        <input type="radio" name="Gender" value="male" checked="checked">Male
        <input type="radio" name="Gender" value="female">Female
    </form>
</div>


گام سوم:
 سپس یک div دیگر ایجاد کنید و نامی مثل mypanel را به آن بدهید تا سطرهایی که ایجاد می‌شوند داخل این div قرار بگیرند.
<div id="mypanel"></div>

گام چهارم:
در بخش head یک تگ اسکریپت باز کرده و کدهای زیر را به آن اضافه می‌کنیم. این کد باعث می‌شود که پلاگین فعال شود.
<script>
$(document).ready(function() {
$("#mypanel").RowAdder();
});
</script>
گام پنجم:
 یک دکمه جهت افزودن سطر به صفحه اضافه می‌کنیم
<button id="addanotherform">Add New Form</button>

و در قسمت تگ اسکریپت هم کد زیر را اضافه می‌کنیم:
$("#addanotherform").on('click', function() {
                $("#mypanel").RowAdder('add');
            });

حال از صفحه تست می‌گیریم: با هر بار کلیک بر روی دکمه‌ی Add New Form یک سطر جدید ایجاد می‌گردد.


در تصویر بالا دکمه‌های دیگر هم دیده می‌شوند که به دیگر متدهای آن اشاره دارد:

جهت مخفی سازی:
 $("#mypanel").RowAdder('hide');

چهت نمایش:
$("#mypanel").RowAdder('show');

جهت افزودن سطر با کد:
$("#mypanel").RowAdder('add');

جهت دریافت تعداد سطرهای ایجاد شده:
$("#mypanel").RowAdder('count')


جهت دریافت کدهای یک سطر در اندیس x

$("#mypanel").RowAdder('content', 3)

جهت حذف یک سطر با اندیس x
$("#mypanel").RowAdder('remove', 3);

همانطور که با صدا زدن اولین متد پلاگین متوجه شدید و نتیجه‌ی آن را در دمو دیدید، این پلاگین از پیش فرض‌هایی جهت راه اندازی اولیه استفاده می‌کند که این پیش فرض‌ها عبارتند از تگ row-sample که بدون معرفی رسمی، آن را شناسایی کرد. همچنین ممکن است بخواهید عبارت Remove را با کلمه‌ی فارسی «حذف» جایگزین نمایید. برای اینکار می‌توانید پلاگین را به شکل زیر به کار ببرید:
    $("#mypanel").RowAdder({
                sample: '#my-custom-sample',
                type: 'text',
                value:'حذف'
        });

تغییر اولین پیش فرض، تغییر نام تگ row-sample به my-custom-sample بود و در مرحله‌ی بعد هم نام فارسی حذف را جایگزین remove کردیم. عبارت type به طور پیش فرض بر روی text قرار دارد که اجباری به ذکر آن در کد بالا نبود. ولی اگر دوست دارید که به جای نمایش عبارت حذف، از یک آیکن یا تصویر استفاده کنید، کد را به شکل زیر تغییر دهید:
  $("#mypanel").RowAdder({
                type: 'image',
                value: 'images/remove.png'
            });
در خطوط بالا عبارت type با image مقدار دهی شد و به پلاگین می‌گوید که به جای متن، از تصویر استفاده کن. همچنین value را به جای متن با آدرس تصویر مقداردهی کرده‌ایم و نتیجه را می‌توانید در دموی قرار گرفته در گیت هاب ببینید.

فایل RowAdder.css
در بردارنده هر سطر
.each-section {
    margin: 20px;
    padding: 5px;
}

جهت استایل بندی لینک چه تصویر و چه متن
.remove-link {
    color:#999;
     text-decoration: none;
}

a:hover.remove-link {
   color:#802727;
}
جهت تغییر استایل بر روی خود تصویر
.remove-image {
    
}

آشنایی با کد پلاگین
(function ($) {
    
    var settings = null;
  $.fn.RowAdder = function (method) {
    
            // call methods
            if (methods[method]) {
                return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
            } else if (typeof method === 'object' || !method) {
                return methods.init.apply(this, arguments);
            } else {
                $.error('Method ' + method + ' does not exist on jQuery.RowAdder');
            }
      

    };
})(jQuery);
در قسمت دوم آموزش پلاگین نویسی برای جی کوئری، متدها به طور واضح توضیح داده شده‌اند. این کدها وظیفه دارند متدهایی را که کاربر درخواست داده است، شناسایی و به همراه آرگومان‌های آن به سمت توابعی که به هر نام متد اختصاص داده‌ایم، ارسال کنند. در صورتیکه متدی با آرگومان‌های ناهماهنگی ارسال شوند، پیام خطایی ارسال می‌گردد و در صورتیکه تعریف نشود، به طور مستقیم init را صدا می‌زند. متغیر settings هم بعدا با تنظیمات پیش فرض پر می‌شود.

متدها
//methods
    var methods = {
        init: function (options) {
            //default-settings
             settings = $.extend({
                'sample': '#row-sample',
                'type': 'text',
                'value': 'Remove'
             }, options);
             this.attr('data-sample', settings.sample);
             this.attr('data-type', settings.type);
             this.attr('data-value', settings.value);
            Do(this);
        },
        show: function () {
            this.css("display", "inline");
        },
        hide: function () {
            this.css("display", "none");
        },
        add: function () {
            Do(this);
        },
        remove: function (index) {
            console.log(index);
           this.find(".each-section")[index].remove();
        },
        content: function (index) {
            return this.find(".each-section")[index];
        },
        count: function (index) {
            return this.find(".each-section").size();
        }
    };
متد init تنظیمات پیش فرض را دریافت می‌نماید و سپس بر روی المانی که پلاگین روی آن واقع شده‌است، مقادیر را ذخیره می‌کند تا در آینده با صدا زدن متدهای دیگر آن را استفاده نماید. کلمه‌ی this در واقع به تگی اشاره می‌کند که پلاگین روی آن اعمال شده است که در مثال‌های بالا mypanel نام داشت. متد Do تابع اصلی ما را در بر دارد که کدهای اصلی پلاگین را شامل می‌شود. مابقی متدها در واقع  جست و جویی بر المان‌ها هستند.

تابع Do
    function Do(panelDiv) {

        settings.sample = panelDiv.data('sample');
        settings.type = panelDiv.data('type');
        settings.value = panelDiv.data('value');
        //find sample code
        var rowsample = $(settings.sample);
        rowsample.css("display", "none");
        var sample = rowsample.html();


        var i = panelDiv.find(".each-section").size();
        //add html details to create a correct template
        var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i });
        var image = $("<img />", { "src": settings.value,"class":"remove-image" });
        var link = $("<a />", { "text": settings.value,"class":"remove-link" });
        //remove event for remove selected form

        //create new form
        sectionDiv.html(sample);

        link.on('click', function (e) {

            e.preventDefault();
            var $this = $(this);
            $this.closest(".each-section").remove();
        });

        if (i > 0) {
            if (settings.type == 'image') {
                link.text('');
                link.append(image);

            }
            sectionDiv.append(link);
        }

        //add new created form on document
        panelDiv.append(sectionDiv);
       
    }
آرگومان داده شده، در واقع همان this هست که به این تابع ارسال شده است. در اولین گام تنظیمات ذخیره شده را که قبلا ذخیره کرده‌ایم، واکشی می‌کنیم. سپس تگ row-sample یا هر نامی را که به آن اختصاص داده شده است، می‌یابیم و محتوای آن را به شکل html در قالب string بیرون می‌کشیم. این کد html در واقع نمونه‌ای است که قرار است در سطر تکرار شود. البته تگ نمونه فقط برای نمونه به کار می‌رود و نیازی نیست روی صفحه نمایش داده شود؛ پس آن را مخفی می‌کنیم. از آنجا که ممکن است این سطری که ایجاد می‌شود، سطر اول نباشد و قبلا هم سطرهایی توسط همین متد ایجاد شده‌اند، بررسی می‌کنیم چند تگ با کلاس each-section داریم. اگر بیشتر از صفر باشد یعنی قبلا سطرهایی ایجاد شده است. در غیر اینصورت این اولین سطر ماست. اولین سطر توسط init صدا زده می‌شود و مابقی توسط متد add انجام می‌گیرد.
        settings.sample = panelDiv.data('sample');
        settings.type = panelDiv.data('type');
        settings.value = panelDiv.data('value');
        //find sample code
        var rowsample = $(settings.sample);
        rowsample.css("display", "none");
        var sample = rowsample.html();


        var i = panelDiv.find(".each-section").size();
در خطوط بعدی یک سری متغیر داریم که برای هر کدام یک قالب تگ div با کلاس‌های مختلف می‌سازیم. sectionDiv یک تگ  div  با کلاس each-section است که هر سطر را به طور کامل در خود قرار می‌دهد. link، جهت ساخت لینک حذف با کلاس remove-link به کار می‌رود. image هم یک تگ image می‌سازد تا اگر کاربر درخواست 'type:'image را داد، به جای لینک متنی حذف، از تصویر استفاده شود.
        //add html details to create a correct template
        var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i });
        var image = $("<img />", { "src": settings.value,"class":"remove-image" });
        var link = $("<a />", { "text": settings.value,"class":"remove-link" });

در خط بعدی محتویات نمونه را داخل تگ sectiondiv قرار می‌دهیم:
//create new form
        sectionDiv.html(sample);

بعد از آن برای رویداد کلیک لینک حذف، کد زیر را وارد می‌کنیم:
   link.on('click', function (e) {

            e.preventDefault();
            var $this = $(this);
            $this.closest(".each-section").remove();
        });
متد closest در جی کوئری این وظیفه را دارد تا به سمت تگ‌های والد تگ this حرکت کند و با برخوردن با اولین تگ والد با کلاس each-section، آن تگ والد را بازگرداند و سپس متد remove را روی آن اجرا کند تا آن تگ به همراه تمام فرزندانش حذف شوند.

اولین شرط زیر بررسی می‌کند که آیا این سطری که ایجاد شده است سطر دوم به بعد است یا خیر؟ اگر آری پس باید دکمه‌ی حذف را به همراه داشته باشد. در صورتیکه سطر دوم به بعد باشد، وارد آن می‌شود. حالا بررسی می‌کند که کاربر برای دکمه‌ی حذف، درخواست لینک تصویری یا لینک متنی داده است و لینک مناسب را ساخته و آن را به انتهای sectionDiv اضافه می‌کند.
   if (i > 0) {
            if (settings.type == 'image') {
                link.text('');
                link.append(image);

            }
            sectionDiv.append(link);
        }

در انتها کل تگ sectionDiv را به تگ داده شده اضافه می‌کنیم تا به کاربر نمایش داده شود.
//add new created form on document
        panelDiv.append(sectionDiv);
مطالب
Blazor 5x - قسمت دوازدهم - مبانی Blazor - بخش 9 - یک تمرین
تا اینجا با مبانی Blazor آشنا شدیم. در این قسمت می‌خواهیم مثالی را بررسی کنیم که بسیاری از این مفاهیم ابتدایی را پوشش می‌دهد. برای نمونه می‌خواهیم یک کامپوننت modal بوت استرپی را جهت دریافت تائیدیه‌ی حذف اتاق‌های تعریف شده‌ی در مثال این سری نمایش دهیم که به همراه مفاهیمی است مانند فرگمنت‌ها جهت تعیین محتوای نمایشی مودال به صورت پویا، ارسال نتیجه‌ی انتخاب بله یا خیر از کامپوننت دریافت تائید، به کامپوننت والد، ارسال پارامترها به کامپوننت فرزند جهت نمایش عنوان و فراخوانی متدهای نمایش و مخفی کردن وهله‌ای از کامپوننت مودال، در کامپوننت والد؛ بدون یک سطر کدنویسی جاوا اسکریپتی!


مرور مثال این قسمت

تا اینجا در مثالی که بررسی کردیم، لیست اتاق‌ها توسط کامپوننت IndividualRoom.razor و لیست خدمات رفاهی یک هتل توسط کامپوننت IndividualAmenity.razor در کامپوننت والد DemoHotel.razor، نمایش داده شده‌اند:


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


بنابراین در ابتدا کامپوننت EditDeleteButton، به کامپوننت IndividualRoom خبر درخواست حذف یک اتاق را می‌دهد. سپس کامپوننت IndividualRoom، یک مودال دریافت تائیدیه‌ی حذف را نمایش می‌دهد. پس از تائید حذف توسط کاربر، این رویداد به کامپوننت DemoHotel، جهت حذف اتاق انتخابی از لیست اتاق‌ها، اطلاع رسانی خواهد شد.


ایجاد کامپوننت مودال دریافت تائید

در ابتدا، فایل جدید Pages\LearnBlazor\LearnBlazor‍Components\Confirmation.razor را ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
@if (ShowModal)
{
    <div class="modal-backdrop show"></div>

    <div class="modal fade show" id="exampleModal" tabindex="-1"
        role="dialog" aria-labelledby="exampleModalLabel"
        aria-hidden="true" style="display: block;">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">
                        @Title
                    </h5>
                    <button @onclick="OnCancelClicked" type="button" class="close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    @ChildContent
                </div>
                <div class="modal-footer">
                    <button @onclick="OnCancelClicked" type="button" class="btn btn-secondary">@CancelButtonLabel</button>
                    <button @onclick="OnConfirmClicked" type="button" class="btn btn-primary">@OkButtonLabel</button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private bool ShowModal;

    [Parameter] public string Title { get; set; } = "Confirm";

    [Parameter] public string CancelButtonLabel { get; set; } = "Cancel";

    [Parameter] public string OkButtonLabel { get; set; } = "Ok";

    [Parameter] public RenderFragment ChildContent { get; set; }

    [Parameter] public EventCallback OnConfirm { get; set; }

    [Parameter] public EventCallback OnCancel { get; set; }

    public void Show() => ShowModal = true;

    public void Hide() => ShowModal = false;

    private async Task OnConfirmClicked()
    {
        ShowModal = false;
        await OnConfirm.InvokeAsync();
    }

    private async Task OnCancelClicked()
    {
        ShowModal = false;
        await OnCancel.InvokeAsync();
    }
}
توضیحات:
- در اینجا در ابتدا تگ‌ها و کلاس‌های مرتبط با نمایش یک modal استاندارد بوت استرپی را مشاهده می‌کنید.
- اگر فیلد خصوصی ShowModal به false تنظیم شود، چون کل محتوای این کامپوننت از DOM حذف خواهد شد (اثر if@ تعریف شده)، سبب مخفی شدن و عدم نمایش آن می‌گردد.
- این کامپوننت عنوان و برچسب‌های دکمه‌های خودش را به صورت پارامتر دریافت می‌کند.
- برای اینکه بتوان محتوای نمایشی این کامپوننت را پویا کرد، از یک RenderFragment استفاده کرده‌ایم:
[Parameter] public RenderFragment ChildContent { get; set; }
- خروجی این کامپوننت به والد یا فراخوان آن، دو رویداد OnConfirm و OnCancel هستند. همچنین چون نمی‌خواهیم کدهای مخفی کردن modal را به ازای هربار کلیک بر روی این دکمه‌ها فراخوانی کنیم، این رویدادها، ابتدا به دو متد خصوصی OnConfirmClicked و OnCancelClicked متصل شده‌اند، تا کار مخفی سازی و سپس هدایت این رویدادها را به کامپوننت والد انجام دهند.
- همچنین می‌خواهیم به کامپوننت فراخوان این امکان را بدهیم تا بتواند به صورت مستقل، سبب نمایش یا مخفی شدن وهله‌ای از این کامپوننت شود. به همین جهت دو متد عمومی Show و Hide نیز تعریف شده‌اند.


هدایت درخواست Delete به کامپوننت نمایش مشخصات اتاق

با توجه به اینکه دکمه‌های حذف و ویرایش هر اتاق، در کامپوننت Pages\LearnBlazor\LearnBlazor‍Components\EditDeleteButton.razor قرار دارند، به آن مراجعه کرده و امکان انتشار این رخ‌داد را به فراخوان آن، با تعریف رویداد OnDelete می‌دهیم:
@if (IsAdmin)
{
    <input type="button" class="btn btn-danger" value="Delete" @onclick="OnDelete" />
    <input type="button" class="btn btn-success" value="Edit" />
}

@code
{
    [Parameter]  public bool IsAdmin { get; set; }

    [Parameter] public EventCallback OnDelete { get; set; }
}


واکنش نشان دادن کامپوننت IndividualRoom.razor به درخواست حذف آن اتاق

کامپوننت Pages\LearnBlazor\LearnBlazor‍Components\IndividualRoom.razor که نمایش دهنده‌ی جزئیات هر اتاق است، با مدیریت رویداد OnDelete کامپوننت EditDeleteButton، از درخواست حذف اتاق جاری مطلع می‌شود:
<EditDeleteButton IsAdmin="true" OnDelete="OnDeleteClicked"></EditDeleteButton>

<Confirmation @ref="Confirmation1"
    OnCancel="OnCancelClicked"
    OnConfirm="@(() => OnDeleteSelectedRoom.InvokeAsync(Room))">
    <div>
        Do you want to delete `@Room.Name`?
    </div>
</Confirmation>
- در اینجا در ابتدا کامپوننت جدید Confirmation را مورد استفاده قرار داده و برای مثال محتوای «آیا می‌خواهید این اتاق را حذف کنید؟»، به صورت پویا به آن ارسال می‌کنیم که در این کامپوننت، توسط فرگمنت مرتبطی نمایش داده می‌شود.
- سپس نیاز است زمانیکه OnDelete کامپوننت EditDeleteButton رخ‌داد، این modal دریافت تائید را نمایش دهیم. به همین جهت باید بتوانیم متد عمومی Show آن‌را فراخوانی کنیم. بنابراین از ref@ برای دسترسی به وهله‌ای از این کامپوننت تعریف شده استفاده کرده‌ایم تا توسط شیء Confirmation1، بتوانیم متد عمومی Show را در رویدادگردان منتسب به OnDelete فراخوانی کنیم.
- همچنین دو رویداد OnCancel و OnConfirm کامپوننت دریافت تائید را به متد خصوصی OnCancelClicked و رویداد جدید OnDeleteSelectedRoom متصل کرده‌ایم. یعنی زمانیکه کاربر بر روی دکمه‌ی OK مودال ظاهر شده کلیک می‌کند، Room جاری، از طریق رویداد OnDeleteSelectedRoom به فراخوان کامپوننت IndividualRoom ارسال می‌شود تا دقیقا بداند که چه اتاقی را بایدحذف کند:
@code
{
    Confirmation Confirmation1;

    [Parameter]
    public BlazorRoom Room { get; set; }

    [Parameter]
    public EventCallback<BlazorRoom> OnDeleteSelectedRoom { get; set; }

    void OnDeleteClicked()
    {
        Confirmation1.Show();
    }

    void OnCancelClicked()
    {
        // Confirmation1.Hide();
    }

   // ...
}
بنابراین کامپوننت IndividualRoom، یک شیء Room را از والد خود دریافت کرده و مشخصات آن‌را نمایش می‌دهد. همچنین پس از تائید حذف این اتاق، آن‌را از طریق رویداد جدید OnDeleteSelectedRoom به والد خود اطلاع رسانی می‌کند.


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

مرحله‌ی آخر این مثال، بسیار ساده‌است. در حلقه‌ای که هر اتاق را توسط کامپوننت IndividualRoom نمایش می‌دهد، به رویداد OnDeleteSelectedRoom گوش فرا داده و selectedRoom یا همان BlazorRoom ارسالی را، دریافت و از لیست Rooms کامپوننت جاری حذف می‌کنیم. این حذف شدن، بلافاصله سبب رندر مجدد UI و حذف آن از رابط کاربری نیز خواهد شد:
@foreach (var room in Rooms)
        {
            <IndividualRoom
                OnRoomCheckBoxSelection="RoomSelectionCounterChanged"
                Room="room"
                OnDeleteSelectedRoom="@(selectedRoom => Rooms.Remove(selectedRoom))">
            </IndividualRoom>
        }


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-12.zip
مطالب
آشنایی با آزمایش واحد (unit testing) در دات نت، قسمت 6

ادامه آشنایی با NUnit

فرض کنید یک RSS reader نوشته‌اید که فیدهای فارسی و انگلیسی را دریافت می‌کند. به صورت پیش فرض هم مشخص نیست که کدام فید اطلاعات فارسی را ارائه خواهد داد و کدامیک انگلیسی. تشخیص محتوای فارسی و از راست به چپ نشان دادن خودکار مطالب ‌آن‌ها به عهده‌ی برنامه نویس است. بهترین روش برای تشخیص این نوع الگوها، استفاده از regular expressions است.
برای مثال الگوی تشخیص اینکه آیا متن ما حاوی حروف انگلیسی است یا خیر به صورت زیر است:

[a-zA-Z]

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

[\u0600-\u06FF]
[ا-یءئ]

در مورد اینکه بازه یونیکد فارسی استاندارد از کجا شروع می‌شود می‌توان به مقاله‌ی آقای حاج‌لو مراجعه نمود (به صورت خلاصه، بازه مصوب عربی یونیکد، همان بازه یونیکد فارسی نیز می‌باشد. یا به بیان بهتر، بازه‌ی فارسی، جزئی از بازه‌ای است که عربی نام گرفته است). البته بازه‌ی مصوب دیگری هم در مورد ایران باستان وجود دارد به نام old Persian که مورد استفاده‌ی روزمره‌ای ندارد!

کلاس زیر را در مورد استفاده از این الگوها تهیه کرده‌ایم:

using System.Text.RegularExpressions;

namespace sample
{
public static class CDetectFarsi
{
public static bool ContainsFarsiData(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, "[ا-یءئ]");
}

public static bool ContainsFarsi(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, @"[\u0600-\u06FF]");
}
}
}

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

using NUnit.Framework;
using sample;

namespace TestLibrary
{
[TestFixture]
public class TestFarsiClass
{
/*******************************************************************************/
[Test]
public void TestContainsFarsi1()
{
Assert.IsTrue("وحید".ContainsFarsi());
}

[Test]
public void TestContainsFarsi2()
{
Assert.IsTrue("گردان".ContainsFarsi());
}

[Test]
public void TestContainsFarsi3()
{
Assert.IsTrue("سپیدTest".ContainsFarsi());
}

[Test]
public void TestContainsFarsi4()
{
Assert.IsTrue("123بررسی456".ContainsFarsi());
}

[Test]
public void TestContainsFarsi5()
{
Assert.IsFalse("Book".ContainsFarsi());
}

[Test]
public void TestContainsFarsi6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsi());
}

[Test]
public void TestContainsFarsi7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsi()); //Russian hello!
}


/*******************************************************************************/
[Test]
public void TestContainsFarsiData1()
{
Assert.IsTrue("وحید".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData2()
{
Assert.IsTrue("گردان".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData3()
{
Assert.IsTrue("سپیدTest".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData4()
{
Assert.IsTrue("123بررسی456".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData5()
{
Assert.IsFalse("Book".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsiData()); //Russian hello!
}
}
}

در کلاس فوق هر دو متد را با آزمایش‌های واحد مختلفی بررسی کرده‌ایم، انواع و اقسام حروف فارسی، ترکیبی از فارسی و انگلیسی، ترکیبی از فارسی و اعداد انگلیسی، عبارت کاملا انگلیسی، عدد کاملا فارسی و یک عبارت روسی! (در یک کلاس عمومی با متدهای عمومی بدون آرگومان از نوع void)
کلاس CDetectFarsi در برنامه اصلی قرار دارد و کلاس TestFarsiClass در یک پروژه class library دیگر قرار گرفته است (در این مورد و جدا سازی آزمایش‌ها از پروژه اصلی در قسمت‌های قبل بحث شد)
همچنین به ازای هر عبارت Assert یک متد ایجاد گردید تا شکست یکی، سبب اختلال در بررسی سایر موارد نشود.
نتیجه اجرای این آزمایش واحد با استفاده از امکانات مجتمع افزونه ReSharper به صورت زیر است:



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

[Test,Ignore]

نکته: مرسوم شده است که نام متدهای آزمایش واحد به صورت زیر تعریف شوند (با Test شروع شوند، در ادامه نام متدی که بررسی می‌شود ذکر گردد و در آخر ویژگی مورد بررسی عنوان شود):

Test[MethodToBeTested][SomeAttribute]

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

مطالب
پردازش‌های Async در Entity framework 6
اجرای Async اعمال نسبتا طولانی، در برنامه‌های مبتنی بر داده، عموما این مزایا را به همراه دارد:

الف) مقیاس پذیری سمت سرور

در اعمال سمت سرور متداول، تردهای متعددی جهت پردازش درخواست‌های کلاینت‌ها تدارک دیده می‌شوند. هر زمانیکه یکی از این تردها، یک عملیات blocking را انجام می‌دهد (مانند دسترسی به شبکه یا اعمال I/O)، ترد مرتبط با آن تا پایان کار این عملیات معطل خواهد شد. با بالا رفتن تعداد کاربران یک برنامه و در نتیجه بیشتر شدن تعداد درخواست‌هایی که سرور باید پردازش کند، تعداد تردهای معطل مانده نیز به همین ترتیب بیشتر خواهند شد. مشکل اصلی اینجا است که نمونه سازی تردها بسیار هزینه بر است (با اختصاص 1MB of virtual memory space) و منابع سرور محدود. با زیاد شدن تعداد تردهای معطل اعمال I/O یا شبکه، سرور مجبور خواهد شد بجای استفاده مجدد از تردهای موجود، تردهای جدیدی را ایجاد کند. همین مساله سبب بالا رفتن بیش از حد مصرف منابع و حافظه برنامه می‌گردد. یکی از روش‌های رفع این مشکل بدون نیاز به بهبودهای سخت افزاری، تبدیل اعمال blocking نامبرده شده به نمونه‌های non-blocking است. به این ترتیب ترد پردازش کننده‌ی این اعمال Async بلافاصله آزاد شده و سرور می‌تواند از آن جهت پردازش درخواست دیگری استفاده کند؛ بجای اینکه ترد جدیدی را وهله سازی نماید.

ب) بالا بردن پاسخ دهی کلاینت‌ها

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

و ... تمام این‌ها یک شرط را دارند. نیاز است یک چنین API خاصی که اعمال Async واقعی را پشتیبانی می‌کنند، فراهم شده باشد. بنابراین صرفا وجود متد Task.Run، به معنای اجرای واقعی Async یک متد خاص نیست. برای این منظور ADO.NET 4.5 به همراه متدهای Async ویژه کار با بانک‌های اطلاعاتی است و پس از آن Entity framework 6 از این زیر ساخت استفاده کرده‌است که در ادامه جزئیات آن‌را بررسی خواهیم کرد.


پیشنیازها

برای کار با امکانات جدید Async موجود در EF 6 نیاز است از VS 2012 به بعد که به همراه کامپایلری است که واژه‌های کلیدی async و await را پشتیبانی می‌کند و همچنین دات نت 4.5 استفاده کرد. چون ADO.NET 4.5 اعمال async واقعی را پشتیبانی می‌کند، دات نت 4 در اینجا قابل استفاده نخواهد بود.


متدهای الحاقی جدید Async در EF 6.x

جهت متدهای الحاقی متداول EF مانند ToList، Max، Min و غیره، نمونه‌های Async آن‌ها نیز اضافه شده‌اند:
 QueryableExtensions:
AllAsync
AnyAsync
AverageAsync
ContainsAsync
CountAsync
FirstAsync
FirstOrDefaultAsync
ForEachAsync
LoadAsync
LongCountAsync
MaxAsync
MinAsync
SingleAsync
SingleOrDefaultAsync
SumAsync
ToArrayAsync
ToDictionaryAsync
ToListAsync

DbSet:
FindAsync

DbContext:
SaveChangesAsync

Database:
ExecuteSqlCommandAsync
بنابراین اولین قدم تبدیل کدهای قدیمی به Async، استفاده از متدهای الحاقی فوق است.


چند مثال


فرض کنید، مدل‌های برنامه، رابطه‌ی one-to-many ذیل را بین یک کاربر و مقالات او دارند:
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; }
    }

    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public int UserId { get; set; }
    }
همچنین Context برنامه نیز جهت در معرض دید قرار دادن این کلاس‌ها، به نحو ذیل تشکیل شده‌است:
    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<BlogPost> BlogPosts { get; set; }

        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }
    }
بر این اساس مثالی که دو رکورد را در جداول کاربران و مقالات به صورت async ثبت می‌کند، به نحو ذیل خواهد بود:
        private async Task<User> addUserAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var user = context.Users.Add(new User
                {
                    Name = "Vahid"
                });
                context.BlogPosts.Add(new BlogPost
                {
                    Content = "Test",
                    Title = "Test",
                    User = user
                });
                await context.SaveChangesAsync(cancellationToken);
                return user;
            }
        }

چند نکته جهت یادآوری مباحث Async

- به امضای متد واژه‌ی کلیدی async اضافه شده‌است، زیرا در بدنه‌ی آن از کلمه‌ی کلیدی await استفاده کرده‌ایم (لازم و ملزوم هستند).
- به انتهای نام متد، کلمه‌ی Async اضافه شده‌است. این مورد ضروری نیست؛ اما به یک استاندارد و قرارداد تبدیل شده‌است.
- مدل Async دات نت 4.5 مبتنی بر Taskها است. به همین جهت اینبار خروجی‌های توابع نیاز است از نوع Task باشند و آرگومان جنریک آن‌ها، بیانگر نوع مقداری که باز می‌گردانند.
- تمام متدهای الحاقی جدیدی که نامبرده شدند، دارای پارامتر اختیاری لغو عملیات نیز هستند. این مورد را با مقدار دهی cancellationToken در کدهای فوق ملاحظه می‌کنید.
نمونه‌ای از نحوه‌ی مقدار دهی این پارامتر در ASP.NET MVC به صورت زیر می‌تواند باشد:
 [AsyncTimeout(8000)]
public async Task<ActionResult> Index(CancellationToken cancellationToken)
در اینجا به امضای اکشن متد جاری، async اضافه شده‌است و خروجی آن نیز به نوع Task تغییر یافته است. همچنین یک پارامتر cancellationToken نیز تعریف شده‌است. این پارامتر به صورت خودکار توسط ASP.NET MVC پس از زمانیکه توسط ویژگی AsyncTimeout تعیین شده‌است، تنظیم خواهد شد. به این ترتیب، اعمال async در حال اجرا به صورت خودکار لغو می‌شوند.
- برای اجرا و دریافت نتیجه‌ی متدهای Async دار EF، نیاز است از واژه‌ی کلیدی await استفاده گردد.

استفاده کننده نیز می‌تواند متد addUserAsync را به صورت زیر فراخوانی کند:
 var user = await addUserAsync();
Console.WriteLine("user id: {0}", user.Id);

شبیه به همین اعمال را نیز جهت به روز رسانی و یا حذف اطلاعات خواهیم داشت:
        private async Task<User> updateAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var user1 = await context.Users.FindAsync(cancellationToken, 1);
                if (user1 != null)
                    user1.Name = "Vahid N.";

                await context.SaveChangesAsync(cancellationToken);
                return user1;
            }
        }

        private async Task<int> deleteAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var user1 = await context.Users.FindAsync(cancellationToken, 1);
                if (user1 != null)
                    context.Users.Remove(user1);

                return await context.SaveChangesAsync(cancellationToken);
            }
        }

کدهای Async تقلبی!

به قطعه کد ذیل دقت کنید:
         public async Task<List<TEntity>> GetAllAsync()
   {
    return await Task.Run(() => _tEntities.ToList());
   }
این متد از یکی از Generic repositoryهای فله‌ای رها شده در اینترنت انتخاب شده‌است.
به این نوع متدها که از Task.Run برای فراخوانی متدهای همزمان قدیمی مانند ToList جهت Async جلوه دادن آن‌ها استفاده می‌شود، کدهای Async تقلبی می‌گویند! این عملیات هر چند در یک ترد دیگر انجام می‌شود اما هم سربار ایجاد یک ترد جدید را به همراه دارد و هم عملیات ToList آن کاملا blocking است.
معادل صحیح Async واقعی این عملیات را در ذیل مشاهده می‌کنید:
        private async Task<List<User>> getUsersAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                return await context.Users.ToListAsync(cancellationToken);
            }
        }
متد ToListAsync یک متد Async واقعی است و نه شبیه سازی شده توسط Task.Run. متدهای Async واقعی کار با شبکه و اعمال I/O، از ترد استفاده نمی‌کنند و توسط سیستم عامل به نحو بسیار بهینه‌ای اجرا می‌گردند.
برای مثال پشت صحنه‌ی متد الحاقی SaveChangesAsync به یک چنین متدی ختم می‌شود:
 internal override async Task<long> ExecuteAsync(
//...
rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
//...
متد ExecuteNonQueryAsync جزو متدهای ADO.NET 4.5 است و برای اجرا نیاز به هیچ ترد جدیدی ندارد.
و یا برای شبیه سازی ToListAsync با ADO.NET 4.5 و استفاده از متدهای Async واقعی آن، به یک چنین کدهایی نیاز است: 
    var connectionString = "........";
    var sql = @"......"";
    var users = new List<User>();
 
    using (var cnx = new SqlConnection(connectionString))
    {
      using (var cmd = new SqlCommand(sql, cnx))
      {
       await cnx.OpenAsync(); 
       using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
       {
        while (await reader.ReadAsync())
        {
          var user = new User
          {
           Id = reader.GetInt32(0), 
           Name = reader.GetString(1), 
          };
         users.Add(user);
        }
       }
      }
    }


محدودیت پردازش موازی اعمال در EF

در متد ذیل، دو Task غیرهمزمان تعریف شده‌اند و سپس با await Task.WhenAll درخواست اجرای همزمان و موازی آن‌ها را کرده‌ایم:
        // multiple operations
        private static async Task loadAllAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var task1 = context.Users.ToListAsync(cancellationToken);
                var task2 = context.BlogPosts.ToListAsync(cancellationToken);

                await Task.WhenAll(task1, task2);
                // use task1.Result
            }
        }
این متد ممکن است اجرا شود؛ یا در بعضی از مواقع با استثنای ذیل خاتمه یابد:
  An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll
 Additional information: A second operation started on this context before a previous asynchronous operation completed.
Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context.
Any instance members are not guaranteed to be thread safe.
متن استثنای ارائه شده بسیار مفید است و توضیحات کامل را به همراه دارد. در EF در طی یک Context اگر عملیات Async شروع شده‌ای خاتمه نیافته باشد، مجاز به شروع یک عملیات Async دیگر، به موازت آن نخواهیم بود. برای رفع این مشکل یا باید از چندین Context استفاده کرد و یا await Task.WhenAll را حذف کرده و بجای آن واژه‌ی کلیدی await را همانند معمول، جهت صبر کردن برای دریافت نتیجه‌ی یک عملیات غیرهمزمان استفاده کنیم.
نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
یک نکته‌ی تکمیلی: امکان فراخوانی کدهای #C از طریق کدهای جاوااسکریپت، در برنامه‌های Blazor

در مطلب جاری، روش فراخوانی توابع جاوااسکریپتی را از طریق کدهای #C برنامه‌های Blazor بررسی کردیم؛ عکس آن نیز میسر است و یکی از کاربردهای آن، ارسال نتایج کتابخانه‌های جاوااسکریپتی، به کدهای یک کامپوننت است. برای مثال کاربری در یک کامپوننت تقویم باز شده، روزی را انتخاب می‌کند. می‌خواهیم نتیجه‌ی این انتخاب او را که در سمت کدهای جاوااسکریپتی رخ‌داده، به نحوی به کدهای #C یک کامپوننت منتقل کنیم و یا حتی محاسباتی را در سمت کدهای #C انجام دهیم و به کدهای جاوااسکریپتی منتقل کنیم.

الف) فراخوانی متدهای استاتیک #C از طریق کدهای جاوااسکریپتی
فرض کنید متد استاتیک HelpMessage را می‌خواهیم از طریق کدهای جاوااسکریپتی فراخوانی کنیم. برای این منظور، یک چنین تابعی باید به ویژگی JSInvokable مزین شود:
@page "/js-sample"


<button class="btn btn-primary" onclick="JsFunctionHelper.invokeDotnetStaticFunction()">Invoke Static Method</button>

@code
{
    [JSInvokable]
    public static Task<string> HelpMessage()
    {
        return Task.FromResult("Help text from C# static function");
    }
}
در اینجا یک دکمه را هم مشاهده می‌کنید که از ویژگی onclick استاندارد HTML استفاده کرده‌است. یعنی متدی را که فراخوانی می‌کند، در حقیقت یک کد جاوا اسکریپتی است و نه یک متد #C واقع در کامپوننت جاری.
سپس در سمت در فایل Client\wwwroot\main.js برای فراخوانی متد HelpMessage خواهیم داشت:
window.JsFunctionHelper = {
  invokeDotnetStaticFunction: function () {
    DotNet.invokeMethodAsync("BlazorRazorSample.Client", "HelpMessage").then(
      (data) => {
        console.log(data);
      }
    );
  }
};
در اینجا تابع سراسری جدیدی به نام invokeDotnetStaticFunction تعریف شده‌است (همان تابعی که توسط دکمه‌ی قرار گرفته در کامپوننت فراخوانی می‌شود). این تابع با استفاده از متد DotNet.invokeMethodAsync استاندارد Blazor، کار فراخوانی متد استاتیک HelpMessage واقع در فضای نام BlazorRazorSample.Client را انجام می‌دهد. چون این فراخوانی async است، نتیجه‌ی نهایی را از طریق یک callback دریافت کرده و لاگ می‌کند.

ب) فراخوانی متدهای غیر استاتیک #C از طریق کدهای جاوااسکریپتی
فراخوانی instance methodهای کامپوننت‌ها از طریق کدهای #C، کمی پیچیده‌تر است:
@page "/js-sample"
@implements IDisposable

@inject IJSRuntime jSRuntime

<button class="btn btn-primary" @onclick="CallInstanceMethod">Invoke Instance Method</button>

@code
{
    private DotNetObjectReference<JsSample> objectReference;

    [JSInvokable]
    public string GetAddress()
    {
        return "123 Main Street";
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if(firstRender)
        {
            objectReference = DotNetObjectReference.Create(this);
        }
    }

    private async Task CallInstanceMethod()
    {
        await jSRuntime.InvokeVoidAsync("JsFunctionHelper.invokeDotnetInstanceFunction", objectReference);
    }

    public void Dispose()
    {
        objectReference?.Dispose();
    }
}
- در این حالت نیاز است ارجاعی از وهله‌ی کامپوننت جاری را به متد جاوااسکریپتی ارسال کرد. به همین جهت در ابتدا توسط متد DotNetObjectReference.Create، این ارجاع را ایجاد کرده و سپس توسط متد jSRuntime.InvokeVoidAsync آن‌را به سمت کدهای جاوا اسکریپتی ارسال می‌کنیم. در مثال فوق، JsSample همان نام کامپوننت جاری است.
- همچنین در اینجا onclick تعریف شده، به متدی داخل همین کامپوننت اشاره می‌کند.
- این ارجاع نیز باید در پایان کار کامپوننت، Dispose شود. به همین جهت implements IDisposable@ را مشاهده می‌کنید.

اکنون کدهای جاوا اسکریپتی که از این وهله‌ی دریافتی استفاده می‌کند، به صورت زیر خواهد بود. در این کدها addressProvider همان objectReference دریافتی است که توسط آن می‌توان متد غیراستاتیک GetAddress کامپوننت را فراخوانی کرد:
window.JsFunctionHelper = {
  invokeDotnetInstanceFunction: function (addressProvider) {
    addressProvider.invokeMethodAsync("GetAddress").then((data) => {
      console.log(data);
    });
  }
};
مطالب
تعریف نوع جنریک به صورت متغیر

در تهیه مثال Auto Mapping به کمک امکانات توکار NH 3.2 به این مورد نیاز پیدا کردم:
بتوان نوع متد جنریک را به صورت متغیر تعریف کرد و این نوع در زمان کامپایل برنامه مشخص نباشد. مثلا چیزی شبیه به این مثال:

using System;

namespace GenericsSample
{
class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}
}

class Program
{
static void Main(string[] args)
{
var type = typeof(Nullable<int>);
TestGenerics.Print<type>(1);
}
}
}

این نوع فراخوانی متد Print در دات نت به صورت پیش فرض غیرمجاز است و نوع جنریک را نمی‌توان به صورت متغیر معرفی کرد.
که البته این هم راه حل دارد و به کمک Reflection قابل حل است:

using System;

namespace GenericsSample
{
class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}
}

class Program
{
static void Main(string[] args)
{
var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
var method = typeof(TestGenerics).GetMethod("Print");
var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
genericMethod.Invoke(null, new object[] { 1 });
}
}
}

دو متد MakeGenericType و MakeGenericMethod برای ساخت پویای نوع‌های جنریک و همچنین ارسال آن‌ها به متدهای جنریک در دات نت وجود دارند که مثالی از نحوه استفاده از آن‌ها را در بالا ملاحظه می‌کنید.

مثال دوم:
اگر کلاس TestGenerics نسخه غیرجنریک متد Print را هم داشت، ‌چطور؟ مثلا:

class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}

public static void Print(object data)
{
Console.WriteLine("Print");
}
}

اینبار اگر برنامه فوق را اجرا کنیم، پیغام Ambiguous match found را حین فراخوانی GetMoethod دریافت خواهیم کرد؛ چون دو متد با یک نام در کلاس یاد شده وجود دارند. برای حل این مشکل باید به نحو زیر عمل کرد:

using System;
using System.Linq;

namespace GenericsSample
{
class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}

public static void Print(object data)
{
Console.WriteLine("Print");
}
}

class Program
{
static void Main(string[] args)
{
var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
var method = typeof(TestGenerics).GetMethods()
.First(x => x.Name == "Print" && (x.GetParameters()[0]).ParameterType.IsGenericParameter);
var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
genericMethod.Invoke(null, new object[] { 1 });
}
}
}

GetMethods تمام متدها را بازگشت داده و سپس بر اساس متادیتای متدها، ‌می‌توان تشخیص داد که کدام یک جنریک است.

مطالب
چگونه از دو نگارش مختلف یک اسمبلی در برنامه‌های NET Core. استفاده کنیم؟
فرض کنید اسمبلی A که System.Drawing.Common، نام دارد، فضای نام System.Drawing.Common را ارائه می‌دهد و اسمبلی B که CoreCompact.System.Drawing نام دارد، باز هم دقیقا همان فضای نام را ارائه می‌دهد. سؤال: آیا می‌توان از این دو اسمبلی مختلف در برنامه‌ی خود استفاده کرد؟ یا مثال دیگر آن داشتن دو اسمبلی با نگارش‌های مختلف، از یک کتابخانه است. برای مثال یکی بر اساس netcoreapp2.1 تهیه شده‌است و دیگری بر اساس netstandard2.0 و به هر دلیلی نیاز است که بتوان از هر دو اسمبلی در برنامه‌ی خود استفاده کرد. برای یک چنین مواردی در زبان #C ویژگی به نام extern alias وجود دارد که با مثالی نحوه‌ی کار با آن‌را بررسی خواهیم کرد.


ایجاد پروژه‌ی Globe1

پروژه‌ی کتابخانه‌ی Globe1 را بر اساس netcoreapp2.1 به این صورت توسط فایل‌های زیر ایجاد می‌کنیم:

الف) فایل Globe1.csproj
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netcoreapp2.1</TargetFramework>
    </PropertyGroup>
</Project>

ب) فایل Globe.cs
using System;

namespace Space
{
    public class Globe
    {
        public string GetColor() => "Blue";
    }
}


ایجاد پروژه‌ی Globe2

پروژه‌ی کتابخانه‌ی Globe2 را بر اساس netstandard2.0 به این صورت توسط فایل‌های زیر ایجاد می‌کنیم:

الف) فایل Globe2.csproj
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
    </PropertyGroup>
</Project>

ب) فایل Globe.cs
using System;

namespace Space
{
    public class Globe
    {
        public string GetColor() => "Green";
    }
}


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

همانطور که ملاحظه می‌کنید اگر این دو پروژه‌ی class library را کامپایل کنیم، به دو فایل dll یا اسمبلی خواهیم رسید که هر دو دارای فضای نام Space و همچنین کلاس Globe هستند. اما نگارش‌های آن‌ها و یا TargetFrameworkهای آن‌ها متفاوت است. اکنون می‌خواهیم از هر دوی این‌ها در یک پروژه‌ی Console استفاده کنیم. بنابراین ابتدا این پروژه را با ایجاد فایل csproj آن شروع می‌کنیم:

الف) فایل Owl.csproj
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp2.1</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <Reference Include="Globe1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
            <HintPath>..\Globe1\bin\$(Configuration)\netcoreapp2.1\Globe1.dll</HintPath>
            <Aliases>Lib1</Aliases>
        </Reference>

        <Reference Include="Globe2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
            <HintPath>..\Globe2\bin\$(Configuration)\netstandard2.0\Globe2.dll</HintPath>
            <Aliases>Lib2</Aliases>
        </Reference>
    </ItemGroup>
</Project>
روش ارجاع دهی به این اسمبلی‌ها، اندکی متفاوت است. در اینجا نام هر کدام به همراه شماره نگارش آن‌ها ذکر شده‌است. همچنین مسیر یافتن فایل‌های Globe1.dll و Globe2.dll نیز ذکر شده‌اند؛ بجای اینکه ارجاعی را به فایل‌های csproj این class libraryها اضافه کنیم (از این جهت که ممکن است مانند مثال ابتدای بحث، صرفا دو فایل dll را بیشتر در اختیار نداشته باشیم). به علاوه یک Alias نیز برای هر کدام تعریف شده‌است تا توسط آن بتوان به امکانات هرکدام دسترسی یافت. وجود این Alias از این جهت ضروری است که هرچند دو فایل dll مختلف را داریم، اما فضاهای نام و کلاس‌های آن‌ها، نام‌های مشابهی دارند و قابل تمیز از یکدیگر نیستند.

ب) فایل Program.cs

اکنون قسمت مهم این فایل، همان extern aliasهایی هستند که پیشتر تعریف کردیم:
extern alias Lib1;
extern alias Lib2;
using System;
using SpaceOne = Lib1::Space;
using SpaceTwo = Lib2::Space;

namespace Owl
{
    class Program
    {
        static void Main(string[] args)
        {
            var owl = new SuperOwl();
            owl.IntegrateGlobe(new SpaceOne.Globe());
            owl.IntegrateGlobe(new SpaceTwo.Globe());

            Console.WriteLine(owl.GetGLobeColors());
        }
    }

    public class SuperOwl
    {
        private SpaceOne.Globe _firstGlobe;
        private SpaceTwo.Globe _secondGlobe;

        public void IntegrateGlobe(SpaceOne.Globe globe) => _firstGlobe = globe;
        public void IntegrateGlobe(SpaceTwo.Globe globe) => _secondGlobe = globe;
        public string GetGLobeColors() => $"First: {_firstGlobe.GetColor()}, Second: {_secondGlobe.GetColor()}";
    }
}
ابتدا extern aliasها تعریف می‌شوند:
extern alias Lib1;
extern alias Lib2;
 سپس بر اساس هر Alias می‌توان یک فضای نام جدید را بجای فضای نام Space فعلی هرکدام تعریف کرد:
using SpaceOne = Lib1::Space;
using SpaceTwo = Lib2::Space;
 تا بتوان به متدهای GetColor متفاوت این‌ها دسترسی یافت.

اگر این برنامه را اجرا کنیم، چنین خروجی حاصل می‌شود:
First: Blue, Second: Green

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: alias.zip