مطالب
مقدمه‌ای بر یادگیری ماشین در #C با استفاده از ML.NET

هنگامی که درباره‌ی علم و یادگیری ماشینی فکر می‌کنیم، دو زبان برنامه‌نویسی بلافاصله به ذهن متبادر می‌شوند: پایتون و R. این دو زبان به شکل عمومی از بسیاری از الگوریتم‌های یادگیری ماشین رایج، تکنیکهای پیش‌پردازش داده‌ها و خیلی بیشتر از اینها پشتیبانی می‌کنند؛ بنابراین برای -تقریباً- هر مساله‌ی یادگیری ماشینی مورد استفاده قرار می‌گیرند. 
 با این‌حال، گاهی فرد یا شرکتی نمی‌تواند از پایتون یا R استفاده کند که می‌تواند به یکی از دلایل متعدد، از جمله وجود کد منبع در زبان دیگر یا نداشتن هیچ تجربه‌ای در پایتون یا R باشد. یکی از محبوب‌ترین زبان‌های امروزی، #C است که برای بسیاری از کاربردها مورد استفاده قرار می‌گیرد. مایکروسافت برای استفاده از قدرت یادگیری ماشین در #C، یک بسته را به نام ML.NET ایجاد کرده که همه‌ی قابلیت‌های یادگیری ماشین پایه را فراهم می‌کند. 
در این مقاله، به شما نشان خواهم داد که چگونه از ML.NET برای ایجاد یک مدل دسته‌بندی دوتایی بهره ببرید؛ قابلیت‌های AutoML را مورد استفاده قرار داده و از یک مدل Tensorflow با ML.NET استفاده کنید. کد کامل مخصوص مدل دسته‌بندی دوتایی را می‌توانید در GitHub بیابید.

افزودن ML.NET به پروژه‌ی #C
اضافه کردن ML.NET به یک پروژه‌ی #C یا #F آسان است. تنها کار لازم، اضافه کردن بسته‌ی Microsoft.ML یا در برخی موارد، -بسته به نیازمندی‌های پروژه- بسته‌های اضافی مانند: Microsoft.ML.ImageAnalytics, Microsoft.ML.TensorFlow یا Microsoft.ML.OnnxTransformer است. 


بارگذاری در یک دیتاست و ایجاد pipeline داده‌ها
بارگذاری و پیش‌پردازش یک مجموعه داده در ML.NET کاملا ً متفاوت از زمانی است که با دیگر بسته‌ها / چارچوب‌های یادگیری ماشین کار می‌کنیم. چون ما نیاز داریم به طور واضح، ساختار داده‌ها را بیان کنیم. برای انجام این کار، فایلی به نام ModelInput.cs را درون یک پوشه به نام DataModels ایجاد کرده و داخل این فایل، همه‌ی ستون‌های مجموعه داده‌های خود را ثبت خواهیم کرد. برای این مقاله، ما از مجموعه داده‌های ردیابی کلاه‌برداری کارت اعتباری استفاده می‌کنیم که می‌تواند آزادانه از Kaggle بارگیری شود. این مجموعه‌داده‌ها شامل ۳۱ ستون است. کلاس تراکنش (۰ یا ۱)، مقدار تراکنش، زمان تراکنش و نیز ۲۸ ویژگی بی‌نام (anonymous). 


using Microsoft.ML.Data;

namespace CreditCardFraudDetection.DataModels
{
    public class ModelInput
    {
        [ColumnName("Time"), LoadColumn(0)]
        public float Time { get; set; }

        [ColumnName("V1"), LoadColumn(1)]
        public float V1 { get; set; }

        [ColumnName("V2"), LoadColumn(2)]
        public float V2 { get; set; }

        [ColumnName("V3"), LoadColumn(3)]
        public float V3 { get; set; }

        [ColumnName("V4"), LoadColumn(4)]
        public float V4 { get; set; }

        [ColumnName("V5"), LoadColumn(5)]
        public float V5 { get; set; }

        [ColumnName("V6"), LoadColumn(6)]
        public float V6 { get; set; }

        [ColumnName("V7"), LoadColumn(7)]
        public float V7 { get; set; }

        [ColumnName("V8"), LoadColumn(8)]
        public float V8 { get; set; }

        [ColumnName("V9"), LoadColumn(9)]
        public float V9 { get; set; }

        [ColumnName("V10"), LoadColumn(10)]
        public float V10 { get; set; }

        [ColumnName("V11"), LoadColumn(11)]
        public float V11 { get; set; }

        [ColumnName("V12"), LoadColumn(12)]
        public float V12 { get; set; }

        [ColumnName("V13"), LoadColumn(13)]
        public float V13 { get; set; }

        [ColumnName("V14"), LoadColumn(14)]
        public float V14 { get; set; }

        [ColumnName("V15"), LoadColumn(15)]
        public float V15 { get; set; }

        [ColumnName("V16"), LoadColumn(16)]
        public float V16 { get; set; }

        [ColumnName("V17"), LoadColumn(17)]
        public float V17 { get; set; }

        [ColumnName("V18"), LoadColumn(18)]
        public float V18 { get; set; }

        [ColumnName("V19"), LoadColumn(19)]
        public float V19 { get; set; }

        [ColumnName("V20"), LoadColumn(20)]
        public float V20 { get; set; }

        [ColumnName("V21"), LoadColumn(21)]
        public float V21 { get; set; }

        [ColumnName("V22"), LoadColumn(22)]
        public float V22 { get; set; }

        [ColumnName("V23"), LoadColumn(23)]
        public float V23 { get; set; }

        [ColumnName("V24"), LoadColumn(24)]
        public float V24 { get; set; }

        [ColumnName("V25"), LoadColumn(25)]
        public float V25 { get; set; }

        [ColumnName("V26"), LoadColumn(26)]
        public float V26 { get; set; }

        [ColumnName("V27"), LoadColumn(27)]
        public float V27 { get; set; }

        [ColumnName("V28"), LoadColumn(28)]
        public float V28 { get; set; }

        [ColumnName("Amount"), LoadColumn(29)]
        public float Amount { get; set; }

        [ColumnName("Class"), LoadColumn(30)]
        public bool Class { get; set; }
    }
} 
در اینجا یک فیلد را برای هر یک از ستون‌های داخل مجموعه داده‌مان ایجاد می‌کنیم. نکته‌ی مهم، تعیین شاخص (Index)، نوع و ستون، به شکل صحیح است. حالا که داده‌های ما مدل‌سازی شده‌اند، باید قالب و شکل داده‌های خروجی خود را مدل کنیم. این کار می‌تواند به روشی مشابه با کدهای بالا انجام شود. 
 using Microsoft.ML.Data;

namespace CreditCardFraudDetection.DataModels
{
    public class ModelOutput
    {
        [ColumnName("PredictedLabel")]
        public bool Prediction { get; set; }

        public float Score { get; set; }
    }
}  
ما در این‌جا ۲ فیلد داریم. فیلد score نشان‌دهنده‌ی خروجی به شکل درصد است؛ در حالیکه فیلد prediction از نوع بولی است. اکنون که هر دو داده ورودی و خروجی را مدل‌سازی کرده‌ایم، می‌توانیم داده‌های واقعی خود را با استفاده از روش مونت‌کارلو بارگذاری کنیم.
IDataView trainingDataView = mlContext.Data.LoadFromTextFile<ModelInput>(
                                            path: dataFilePath,
                                            hasHeader: true,
                                            separatorChar: ',',
                                            allowQuoting: true,
                                            allowSparse: false);

ساخت و آموزش مدل
برای ایجاد و آموزش مدل، نیاز به ایجاد یک pipeline داریم که شامل پیش‌پردازش داده‌های مورد نیاز و الگوریتم آموزش است. برای این مجموعه داده‌ی خاص، انجام هر پیش‌پردازش بسیار دشوار است زیرا ۲۸ ویژگی بی‌نام دارد. بنابراین تصمیم گرفتم که آن را ساده نگه دارم و تنها همه‌ی ویژگی‌ها را الحاق کنم (این کار باید در ML.NET انجام شود).
var dataProcessPipeline = mlContext.Transforms.Concatenate("Features", new[] { "Time", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", "V16", "V17", "V18", "V19", "V20", "V21", "V22", "V23", "V24", "V25", "V26", "V27", "V28", "Amount" });
برای مدل، الگوریتم LightGBM را انتخاب می‌کنم. این الگوریتم در واقع در Microsoft.ML از ابتدا وجود ندارد؛ بنابراین شما باید Microsoft.ML.LightGbm را نصب کنید تا قادر باشید از آن استفاده کنید.
// Choosing algorithm
var trainer = mlContext.BinaryClassification.Trainers.LightGbm(labelColumnName: "Class", featureColumnName: "Features");

// Appending algorithm to pipeline
var trainingPipeline = dataProcessPipeline.Append(trainer);
اکنون می‌توانیم مدل را با متد Fit، آموزش داده سپس با استفاده از mlContext.model.save ذخیره کنیم:
ITransformer model = trainingPipeline.Fit(trainingDataView);
mlContext.Model.Save(model , trainingDataView.Schema, <path>);

ارزیابی مدل
حالا که مدل ما آموزش دیده است، باید عملکرد آن را بررسی کنیم. ساده‌ترین راه برای انجام این کار، استفاده از اعتبارسنجی متقاطع (cross-validation) است. ML.Net به ما روش‌های اعتبارسنجی متقاطع را برای انواع مختلف داده‌های مختلف، ارایه می‌دهد. از آنجا که مجموعه داده‌های ما یک مجموعه داده دسته‌بندی دودویی است، ما از روش mlContext.BinaryClassification.CrossValidateNonCalibrated برای امتیازدهی به مدل خود استفاده خواهیم کرد:
var crossValidationResults = mlContext.BinaryClassification.CrossValidateNonCalibrated(trainingDataView, trainingPipeline, numberOfFolds: 5, labelColumnName: "Class");

انجام پیش‌بینی
پیش بینی داده‌های جدید با استفاده از ML.NET واقعاً سرراست و راحت است. ما فقط باید یک PredictionEngine، نمایشی دیگر را از مدل خود که به طور خاص، برای استنباط ساخته شده است، ایجاد کنیم و متد Predict آن را به عنوان یک شی ModelInput فراخوانی کنیم. 
var predEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel);

ModelInput sampleData = new ModelInput() {
    time = 0,
    V1 = -1.3598071336738,
    ...
};

ModelOutput predictionResult = predEngine.Predict(sampleData);

Console.WriteLine($"Actual value: {sampleData.Class} | Predicted value: {predictionResult.Prediction}"); 

Auto-ML 
نکته جالب دیگر در مورد ML.NET اجرای عالی Auto ML است. با استفاده از  Auto ML  فقط با مشخص کردن اینکه روی چه مشکلی کار می‌کنیم و ارائه داده‌های خود، می‌توانیم راه‌حل‌های اساسی و پایه‌ی یادگیری ماشین را بسازیم.
برای شروع کار با ML خودکار در ML.NET، باید  Visual Studio Extension - ML.NET Model Builder (Preview)  را بارگیری کنیم. این کار را می‌توان از طریق تب extensions انجام داد.
پس از نصب موفقیت آمیز افزونه، با کلیک راست روی پروژه‌ی خود در داخل Solution Ex می‌توانیم از Auto ML استفاده کنیم.

با این کار پنجره Model Builder باز می‌شود. سازنده‌ی مدل، ما را در روند ساخت یک مدل یادگیری ماشین راهنمایی می‌کند.
   
برای کسب اطلاعات در مورد چگونگی گذراندن مراحل مختلف، حتماً آموزش رسمی شروع کار را در سایت مایکروسافت، بررسی کنید. بعد از تمام مراحل، Model Builder به طور خودکار کد را تولید می‌کند.
 
استفاده از یک مدل پیش‌آموزش‌داده‌شده‌ی تنسورفلو (pre-trained) 
نکته‌ی جالب دیگر در مورد ML.NET این است که به ما امکان استفاده از مدل‌های Tensorflow و ONNX را برای استنباط ( inference ) می‌دهد. برای استفاده از مدل Tensorflow باید Microsoft.ML.TensorFlow را با استفاده از NuGet نصب کنیم. پس از نصب بسته‌های لازم، می‌توانیم با فراخوانی متد Model.LoadTensorFlowModel، یک مدل Tensorflow را بارگذاری کنیم. پس از آن، باید متد ScoreTensorFlowModel را فراخوانی کرده و نام لایه‌ی ورودی و خروجی را به آن ارسال کنیم.  
private ITransformer SetupMlnetModel(string tensorFlowModelFilePath)
{
    var pipeline = _mlContext.<preprocess-data>
           .Append(_mlContext.Model.LoadTensorFlowModel(tensorFlowModelFilePath)
                                               .ScoreTensorFlowModel(
                                                      outputColumnNames: new[]{TensorFlowModelSettings.outputTensorName },
                                                      inputColumnNames: new[] { TensorFlowModelSettings.inputTensorName },
                                                      addBatchDimensionInput: false));
 
    ITransformer mlModel = pipeline.Fit(CreateEmptyDataView());
 
    return mlModel;
}

اطلاعات بیشتر در مورد نحوه استفاده از مدل Tensorflow در ML.NET:
نظرات مطالب
نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Tooltip به کمک Twitter bootstrap
با سلام
چرا بعد از عملیات ajax ایی دیگه کار نمیده؟
از کد زیر برای نمایش استفاده شده:
<script type="text/javascript">
    $(document).ready(function () {
        $("[rel='tooltip']").tooltip({ placement: 'top', trigger: 'hover' });
    });
</script>

ولی بعد از انجام این دستور دیگه کار نمیده:
@Ajax.ActionLink(" ", MVC.Admin.ContactUs.ActionNames.List, MVC.Admin.ContactUs.Name,
            new
            {
                bywriter = ViewBag.bywriter,
                bydate = ViewBag.bydate,
                byisread = ViewBag.byisread,
                byisshow = ViewBag.byisshow,
                page = max,
                count = ViewBag.COUNT
            },
                new AjaxOptions
                {
                    HttpMethod = "Post",
                    InsertionMode = InsertionMode.Replace,
                    OnBegin = "showLoading",
                    UpdateTargetId = "listdiv",
                    OnComplete = "hideLoading"
                },
            new { @class = "glyphicon glyphicon-backward nodecoration", @rel = "tooltip", @title = "صفحه آخر" })
نظرات مطالب
طراحی گزارش در Stimulsoft Reports.Net – بخش 1
سلام
متغیر نرمال را طبق توضیح گفته شده اضافه کردم و در Data Rendering هم کد Variable1 + = Products . UnitPrice  را نوشتم اما در Preview مقدار را 0 نمایش می‌دهد. 
همچنین برای نمایش sum در پایین تمام صفحه‌ها وقتی گزینه Running Total را تیک میزنم در پایین هیچ کدام از صفحه‌ها حتی صفحه آخر sum را نمایش نمی‌دهد و تیک آن را برمی دارم sum در صفحه آخر نمایش داده می‌شود.
من با live demo برنامه در آدرس http://mobile.stimulsoft.com/ کار می‌کنم.
مطالب
تبدیل بلوک‌های یونیکد در زیرنویس برای نمایش در تلویزیون‌ها و پلیرها
مقدمه
موقعی که سینمای ناطق کار خود را آغاز کرد، بسیاری از مردم از آن استقبال کردند و بسیاری از سینماگران که این استقبال را دیدند، رفته رفته به سمت سینمای ناطق کشیده شدند. ولی در این بین یک مشکلی ایجاد شده بود؛ اینکه ناشنوایان دیگر مانند قدیم یعنی دوران صامت نمی‌توانستند فیلم‌ها را تماشا کنند، پس نیاز بود این مشکل به نحوی رفع شود. از اینجا بود که ایده‌ی زیرنویس شکل گرفت و این مشکل را رفع نمود. بعدها فیلم‌ها انتقال دهنده‌ی فرهنگ و پیوند دهنده‌ی مردم با فرهنگ‌های مختلف شدند ولی تفاوت در زبان باعث می‌شد که این امر به خوبی صورت نگیرد. به همین علت زیرنویس، وظیفه‌ی دیگری را هم پیدا کرد و آن رساندن پیام فیلم با زبان خود مخاطب بود. امروزه تهیه‌ی زیرنویس‌ها توسط بسیاری از افراد که با زبان انگلیسی (آشنایی با یک زبان میانی برای ترجمه زیرنویس) آشنایی دارند رواج پیدا کرده و روزانه نزدیک به صد زیرنویس یا گاها بیشتر با زبان‌های مختلف بر روی اینترنت قرار می‌گیرند. بزرگترین سایتی که در حال حاضر با شهرت جهانی در این زمینه فعالیت دارد سایت  subscene.com  است.

آشنایی با انواع زیرنویس‌ها
زیرنویس‌ها فرمت‌های مختلفی دارند مانند srt,sub idx,smi و ... ولی در حال حاضر معروف‌ترین و معتبرترین فرمت در بین همه‌ی فرمت‌ها Subrip  با پسوند SRT می‌باشد که قالب متنی به صورت زیر دارد:
203
00:16:38,731 --> 00:16:41,325
<i>Happy Christmas, your arse
I pray God it's our last</i>
که باعث میشود حجم بسیار کمی در حد چند کیلوبایت داشته باشد.

بررسی مشکل ما با زیرنویس در تلویزیون‌ها
یکی از مشکلاتی که ما در اجرای زیرنویس‌ها بر روی تلویزیون‌ها داریم این است که حروف فارسی را به خوبی نمی‌شناسند و در هنگام نمایش با مشکل مواجه می‌شوند که البته در اکثر مواقع با تبدیل زیرنویس از ANSI به Unicode یا UTF-8 مشکل حل می‌شود. ولی در بعضی مواقع تلویزیون یا پلیرها از پشتیبانی زبان فارسی سرباز می‌زنند و زیرنویس را به شکل زیر نمایش می‌دهند.
سلام = م ا ل س
به این جهت ما از یک برنامه به اسم srttouni استفاده می‌کنیم که با استفاده یک روش جایگزینی و معکوس سازی، مشکل ما را حل می‌کند. ولی باز هم این برنامه مشکلاتی دارد و از آنجا که برنامه نویس این برنامه که واقعا کمال تشکر را از ایشان، دارم مشخص نیست، مجبور شدم به جای گزارش، خودم این مشکلات را حل کنم. 
مشکلات این برنامه :
  • عدم حذف تگ‌ها ، گاها برنامه نویس‌ها از تگ هایی چون Bold,italic,underline,color استفاده می‌کنند که معدود برنامه‌هایی آن را پشتیبانی کرده و تلویزیون و پلیرها هم که اصلا پشتیبانی نمی‌کنند و باعث میشود که متن روی تلویزیون مثل کد html ظاهر شود
  • بعضی جملات دوبار روی صفحه ظاهر می‌شوند.
  • تنها یک فایل را در هر زمان تبدیل می‌کند. مثلا اگر یک سریال چند قسمته داشته باشید، برای هر قسمت باید زیرنویس را انتخاب کرده و تبدیل کنید، در صورتی که میتوان دستور داد تمام زیرنویس‌های داخل دایرکتوری را تبدیل کرد یا چند زیرنویس را برای این منظور انتخاب کرد.

نحوه‌ی خواندن زیرنویس با کدنویسی
با تشکر از دوست عزیز ما در این صفحه می‌توان گفت یک کد تقریبا خوب و جامعی را برای خواندن این قالب داریم. بار دیگر نگاهی به قالب یک دیالوگ در زیرنویس می‌اندازیم و آن را بررسی می‌کنیم:
203
00:16:38,731 --> 00:16:41,325
<i>Happy Christmas, your arse
I pray God it's our last</i>
اولین خط شامل شماره‌ی خط است که از یک آغاز می‌گردد تا به تعداد دیالوگ‌ها، خط دوم، زمان آغاز و پایان دیالوگ مورد نظر است، موقعی که دیالوگ روی صفحه ظاهر میشود تا موقعی که دیالوگ از روی صفحه محو شود که به ترتیب بر اساس ساعت:دقیقه:ثانیه و میلی ثانیه می‌باشد. خطوط بعدی هم متن دیالوگ است است و بعد از پایان متن دیالوگ یک خط خالی زیر آن قرار می‌گیرد تا نشان دهد این دیالوگ به پایان رسیده است. اگر همین خط خالی حذف گردد برنامه‌هایی چون Media player classic خطهای زیری را جز متن دیالوگ قبلی به حساب می‌آورند و شماره خط و زمان بندی دیالوگ بعدی به عنوان متن روی صفحه ظاهر می‌گردند و بعضی player‌ها هم قاطی کرده و کلا زیرنویس را نمی‌خوانند یا اون خط رو نشون نمیدن مثل Kmplayer و هر کدام رفتار خاص خودشان را بروز می‌دهند.
کد زیر در کلاس SubRipServices وظیفه‌ی خواندن محتوای فایل srt را بر اساس عبارتی که دادیم دارد:
private readonly static Regex regex_srt = new Regex(@"(?<sequence>\d+)\r\n(?<start>\d{2}\:\d{2}\:\d{2},\d{3}) --\> " +
            @"(?<end>\d{2}\:\d{2}\:\d{2},\d{3})\r\n(?<text>[\s\S]*?)\r\n\r\n", RegexOptions.Compiled);

 public string ToUnicode(string lines)
        {

        string subtitle= regex_srt.Replace(lines,delegate(Match m)
             {
                 string text = m.Groups["text"].Value;
                 //1.remove tags
                 text = CleanScriptTags(text);

                 //2.replace letters
                 PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
                 string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
                 return
                     string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value,
                         m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ;
             }
            );

            return subtitle;
        }
در اولین خط ما یک Regular Expersion یا یک عبارت با قاعده تعریف کردیم که در اینجا میتوانید با خصوصیات آن آشنا شوید. ما برای این کلاس یک الگو ایجاد کردیم و بر حسب این الگو، متن یک زیرنویس را خواهد گشت و خطوطی را که با این تعریف جور در می‌آیند و معتبر هستند، برای ما باز می‌گرداند.
عبارتهایی که به صورت <name>? تعریف شده‌اند در واقع یک نامگذاری برای هر قسمت از الگوی ما هستند تا بعدا این امکان برای ما فراهم شود که خطوط برگشتی را تجزیه کنیم که مثلا فقط قسمت متن را دریافت کنیم، یا فقط قسمت زمان شروع یا پایان را دریافت کنیم و ...
متد tounicode یک آرگومان متنی دارد (lines) که شامل محتویات فایل  زیرنویس است. متد Replace در شی regex_srt با هر بار پیدا کردن یک متن بر اساس الگو در رشته lines دلیگیتی را فرا می‌خواند که در اولین پارامتر آن که از نوع matchEvaluator است، شامل اطلاعات متنی است که بر اساس الگو، یافت شده است. خروجی آن از نوع string می‌باشد که با متن پیدا شده بر اساس الگو جابجا خواهد کرد و در نهایت بعد از چندین بار اجرا شدن، کل متن‌های تعویض شده، به داخل متغیر subtitle ارسال خواهند شد.
کاری که ما در اینجا می‌کنیم این است که هر دیالوگ داخل زیرنویس را بر اساس الگو، یافته و متن آن را تغییر داده و متن جدید را جایگزین متن قبلی می‌کنیم. اگر زیرنویس ما 800 دیالوگ داشته باشد این دلیگیت 800 مرتبه اجرا خواهد شد.
از آنجا که ما تنها می‌خواهیم متن زیرنویس را تغییر دهیم، در اولین خط فرامین این دلیگیت تعریف شده، متن مورد نظر را بر اساس همان گروه‌هایی که تعریف کرده‌ایم دریافت می‌کنیم و در متغیر text قرار می‌دهیم:
m.Groups["text"].Value
در مرحله‌ی بعدی ما اولین مشکلمان (حذف تگ‌ها)  را با تابعی به اسم CleanScriptTags برطرف میکنیم که کد آن به شرح زیر است:
 private static readonly Regex regex_tags = new Regex("<.*?>", RegexOptions.Compiled);
 private  string CleanScriptTags(string html)
        {
            return regex_tags.Replace(html, string.Empty);
        }
کد بالا از یک regular Expression دیگر جهت پیدا کردن تگ‌ها استفاده می‌کند و به جای آن‌ها عبارت "" را جایگزین می‌کند. این کد قبلا در سایت جاری در این صفحه توضیح داده شده است. خروجی این تابع را مجددا در text قرار می‌دهیم و به مرحله‌ی دوم، یعنی تعویض کاراکترها می‌رویم:
 PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
                 string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
برای اینکه دقیقا متوجه شویم قرار است چکاری انجام شود بیاید دو گروه یا بلوک مختلف در یونیکد را بررسی کنیم. هر بلوک کد در یونیکد شامل محدوده‌ای از کد پوینت هاست که نامی منحصرفرد برای خود دارد و هیچ کدام از کدپوینت‌ها در هر بلوک یا گروه، اشتراکی با بقیه‌ی بلوک‌ها ندارد. سایت codetable از آن دست سایت‌هایی است که اطلاعات خوبی در مورد کدهای یونیکد دارد. در قسمت Unicode Groups دو گروه برای زبان عربی وجود دارند که در جدول این گروه، هر سطر آن یکی از کدها را به صورت دسیمال، هگزا دسیمال و نام و نماد آن، نمایش می‌دهد.
^  ,   ^   Arabic Presentation Forms-A 
^^  Arabic Presentation Forms-B 
بلوک اول طبق گفته‌ی ویکی پدیا دسته‌ی متنوعی از حروف مورد نیاز برای زبان فارسی، اردو، پاکستانی و تعدادی از زبان‌های آسیای مرکزی است.
بلوک دوم شامل نمادها و نشانه‌های زبان عربی است و در حال حاضر برای کد کردن استفاده نمی‌شوند و دلیل حضور آن برای سازگاری با سیستم‌های قدیمی است.
اگر خوب به مشکلی که در بالا برای زیرنویس‌ها اشاره کردیم دقت کنید، گفتیم حروف از هم جدا نشان داده می‌شوند و اگر به بلوک دوم در لینک‌های داده شده نگاه کنید می‌بینید که حروف متصل را داراست. یعنی برای حرف س 4 حرف یا کدپوینت داراست : سـ برای کلماتی مثل سبد، ـس برای کلماتی مثل شانس، ـسـ برای کلماتی مثل بسیار، ولی خود س برای کلمات غیر متصل مثل ناس، البته بعضی حروف یک یا دو حالت می‌طلبند مثل د، ر که فقط دو حالت ـد و د ، ـر و ر را دارند یا مثل آ که یک حالت دارد.
من قبلا یک کلاس به نام lettersTable ایجاد کرده بودم (و دیگر نوشتن آن را ادامه ندادم) که برای هر حرف، یک آیتم در شی‌ءایی از نوع dictionary ساخته بودم و هر کدپوینت بلوک اول را در آن کلید و کد متقابلش را در بلوک دوم، به صورت مقدار ذخیره کرده بودم (گفتیم که هر نماد در بلوک اول، برابر با 4 نماد در بلوک دوم است؛ ولی ما در دیکشنری تنها مقدار اول را ذخیره می‌کنیم. زیرا کد بقیه نمادها دقیقا پشت سر یکدیگر قرار گرفته‌اند که می‌توان با یک جمع ساده از عدد 0 تا 3، به مقدار هر کدام از نمادها رسید. البته ناگفته نماند بعضی نمادها 2 عدد بودند که این هم باید بررسی شود). برای همین هر کاراکتر را با کاراکتر قبل و بعد می‌گرفتم و بررسی می‌کردم و از یک جدول دیکشنری دیگر هم به اسم specialchars هم استفاده کردم تا آن کاراکترهایی که تنها دو نماد یا یک نماد را دارند، بررسی کنم و این کاراکترها همان کاراکترهایی بودند که اگر قبل یک حرف هم بیایند، حرف بعدی به آن‌ها نمی‌چسبد. برای درک بهتر، این عبارت مثال زیر را  برای حرف س در نظر بگیرید:
مستطیل = چون بین هر دو طرف س حر وجود دارد قطعا باید شکل س به صورت ـسـ انتخاب شود ، حالا مثال زیر را در نظر بگیرید:
دست = دـست که اشتباه است و باید باشد دست یعنی شکل سـ باید صدا زده شود، پس این مورد هم باید لحاظ شود.
نمونه‌ای از کد این کلاس:
Dictionary<int ,int>  letters=new Dictionary<int, int>();

   //0=0x0 ,1=1x0 ,2=0x1 ,3=1x1
        private void FillPrimaryTable()
        {
            //آ
            letters.Add(1570, 65153);
            //ا
            letters.Add(1575, 65166);
            //أ
            letters.Add(1571, 65155);
            //ب
            letters.Add(1576, 65167);
            //ت
            letters.Add(1578, 65173);
            //ث
            letters.Add(1579, 65177);
            //ج
            letters.Add(1580, 65181);
.....
}

Dictionary<int,byte> specialchars=new Dictionary<int, byte>();

  private void SetSpecialChars()
        {
            //آ
            specialchars.Add(1570, 0);
            //ا
            specialchars.Add(1575, 0);
            //د2
            specialchars.Add(1583, 1);
            //ذ2
            specialchars.Add(1584, 1);
            //ر2
            specialchars.Add(1585, 1);
            //ز2
            specialchars.Add(1586, 1);
            //ژ
            specialchars.Add(1688, 1);
            //و2
            specialchars.Add(1608, 1);
            //أ
            specialchars.Add(1571, 1);

        }
کلاس بالا تنها برای ذخیره‌ی کدپوینت‌ها بود، ولی یک کلاس دیگر هم به اسم lettersCrawler نوشته بودم که متد آن وظیفه‌ی تبدیل را به عهده داشت.

در آن متد هر بار یک حرف را انتخاب می‌کرد و حرف قبلی و بعدی آن را ارسال می‌کرد تا تابع CalculateIncrease آن را محاسبه کرده و کاراکتر نهایی را باز گرداند و به متغیر finalText اضافه می‌کرد. ولی در حین نوشتن، زمانی را به یاد آوردم که اندروید به تازگی آمده بود و هنوز در آن زمان از زبان فارسی پشتیبانی نمی‌کرد و حروف برنامه‌هایی که می‌نوشتیم به صورت جدا از هم بود و همین مشکل را داشت که ما این مشکل را با استفاده از یک کلاس جاوا که دوست عزیزی آن را در اینجا به اشتراک گذاشته بود، حل می‌کردیم. پس به این صورت بود که از ادامه‌ی نوشتن کلاس انصراف دادم و از یک کلاس دقیق‌تر و آماده استفاده کردم.
در واقع این کلاس همین کار بالا را با روشی بهتر انجام می‌دهد. همه‌ی نمادها به طور دقیق‌تری کنترل می‌شوند حتی تنوین‌ها و دیگر علائم، همه نمادها با کدهای متناظر در یک آرایه ذخیره شده‌اند که ما در بالا از نوع Dictionary استفاده کرده بودیم.
تنها کاری که نیاز بود، باید این کد به سی شارپ تبدیل میشد و از آنجایی که این دو زبان خیلی شبیه به هم هستند، حدود ده دقیقه‌ای برای ویرایش کد وقت برد که می‌توانید کلاس نهایی را از اینجا دریافت کنید.
پس خط زیر در متد ToUnicode کار تبدیل اصلی را صورت می‌دهد:
  PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
بنابراین مرحله‌ی دوم انجام شد. این تبدیل در بسیاری از سیستم‌ها همانند اندروید کافی است؛ ولی ما گفتیم که تلویزیون یا پلیر به غیر از جدا جدا نشان دادن حروف، آن‌ها را معکوس هم نشان می‌دهند. پس باید در مرحله‌ی بعد آن‌ها را معکوس کنیم که اینکار با خط زیر و صدا زدن تابع ReverseText انجام میگیرد
 //3.reverse tags
                 text = ReverseText(text);
از آنجا که یک دیالوگ ممکن است چند خطی باشد، این معکوس سازی برای ما دردسر می‌شد و ترتیب خطوط هم معکوس می‌شد. پس ما با استفاده از کد زیر هر یک خط را شکسته و هر کدام را جداگانه معکوس می‌کنیم و سپس به یکدیگر می‌چسبانیم:
string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
همه‌ی ما معکوس سازی یک رشته را بلدیم، یکی از روش‌ها این است که رشته را خانه به خانه از آخر به اول با یک for بخوانیم یا اینکه رشته را به آرایه‌ای از کارکاکترها، تبدیل کنیم و سپس با Array.Reverse آن را معکوس کرده و خانه به خانه به سمت جلو بخوانیم و خیلی از روش‌های دیگر. ولی این معکوس سازی‌ها برای ما یک عیب هم دارد و این هست که این معکوس سازی روی نمادهایی چون . یا ! و  غیره که در ابتدا و انتهای رشته آمده‌اند و حروف انگلیسی، نباید اتفاق بیفتند. پس می‌بینیم که تابع معکوس سازی هم باز باید ویژه‌تر باشد. ابتدا قسمت‌های ابتدا و انتها را جدا کرده و از آن حذف می‌کنیم. سپس رشته را معکوس می‌کنیم. ولی ممکن هست و احتمال دارد که بین حروف فارسی هم حروف انگلیسی یا اعداد به کار رود که آن‌ها هم معکوس می‌شوند. برای همین بعد از معکوس سازی یکبار هم باید آن‌ها را با یک عبارت با قاعده یافته و سپس هر کدام را جداگانه معکوس کرده و سپس مثل روش بالا Replace کنیم و رشته‌های جدا شده را به ابتدا و انتهای آن، سر جای قبلیشان می‌چسبانیم.
این دو تابع برای معکوس کردن عادی یک رشته به کار می‌روند:
    private string Reverse(string text)
        {
            return Reverse(text,0,text.Length);
        }

        private string Reverse(string text,int start,int end)
        {
            if (end < start)
                return text;
            string reverseText = "";

            for (int i = end-1; i >=start; i--)
            {
                reverseText += text[i];
            }
            return reverseText;
        }
ولی این تابع ReverseText جمعی از عملیات معکوس سازی ویژه‌ی ماست؛ مرحله اول، مرحله دریافت و ذخیره‌ی حروف خاص در ابتدای رشته به اسم پیشوند prefix است:
  private string ReverseText(string text)
        {
            char[] chararray = text.ToCharArray();
            string reverseText = "";
            bool prefixcomp = false;
            bool postfixcomp = false;
            string prefix = "";
            string postfix = "";

            #region get prefix symbols
            for (int i = 0; i < chararray.Length; i++)
            {
                if (!prefixcomp)
                {
                    char ch =(char) chararray.GetValue(i) ;
                    if (ch< 130)
                    {
                        prefix += chararray.GetValue(i);
                    }
                    else
                    {
                        prefixcomp = true;
                        break;
                    }
                }
            }
            #endregion
}
مرحله‌ی دوم هم دریافت و ذخیره‌ی حروف خاص در انتهای رشته به اسم پسوند postfix است که به این تابع اضافه می‌کنیم:
 #region get postfix symbols
            for (int i = chararray.Length - 1; i >-1 ; i--)
            {
                if (!postfixcomp && prefix.Length!=text.Length)
                {
                    char ch = (char)chararray.GetValue(i);
                    if (ch < 130)
                    {
                        postfix += chararray.GetValue(i);
                    }
                    else
                    {
                        postfixcomp = true;
                        break;
                    }
                }
            }
            #endregion
مرحله‌ی سوم عملیات معکوس سازی روی رشته است و سپس با استفاده از یک Regular Expression حروف انگلیسی و اعداد بین حروف فارسی را یافته و یک معکوس سازی هم روی آن‌ها انجام می‌دهیم تا به حالت اولشان برگردند. کل عملیات معکوس سازی در اینجا به پایان می‌رسد:
  #region reverse text

            reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length);

        
            reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m)
            {
                return Reverse(m.Value);
            });
            #endregion
تعریف عبارت با قاعده‌ی بالا به اسم unTargetedLetters:
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled);
آخر سر هم رشته را به‌علاوه پیشوند و پسوند جدا شده بر می‌گردانیم:
return prefix+ reverseText+postfix;
کد کامل تابع بدین شکل در می‌آید:
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled);
private string ReverseText(string text)
        {
            char[] chararray = text.ToCharArray();
            string reverseText = "";
            bool prefixcomp = false;
            bool postfixcomp = false;
            string prefix = "";
            string postfix = "";

            #region get prefix symbols
            for (int i = 0; i < chararray.Length; i++)
            {
                if (!prefixcomp)
                {
                    char ch =(char) chararray.GetValue(i) ;
                    if (ch< 130)
                    {
                        prefix += chararray.GetValue(i);
                    }
                    else
                    {
                        prefixcomp = true;
                        break;
                    }
                }
            }
            #endregion

            #region get postfix symbols
            for (int i = chararray.Length - 1; i >-1 ; i--)
            {
                if (!postfixcomp && prefix.Length!=text.Length)
                {
                    char ch = (char)chararray.GetValue(i);
                    if (ch < 130)
                    {
                        postfix += chararray.GetValue(i);
                    }
                    else
                    {
                        postfixcomp = true;
                        break;
                    }
                }
            }
            #endregion

            #region reverse text

            reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length);

        
            reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m)
            {
                return Reverse(m.Value);
            });
            #endregion

          

            return prefix+ reverseText+postfix;
        }
در نهایت، خط آخر دلیگت همه چیز را طبق فرمت یک دیالوگ srt چینش کرده و بر می‌گردانیم.
return
                     string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value,
                         m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ;
رشته subtitle را به صورت srt ذخیره کرده و انکودینگ را هم Unicode انتخاب کنید و تمام.

نمایی از برنامه‌ی نهایی


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


روی پلیر یا تلویزیون



  نکته‌ی نهایی: هنگام تست زیرنویس روی فیلم متوجه شدم پلیر خطوط بلند را که در صفحه‌ی نمایش جا نمی‌شود، می‌شکند و به دو خط تقسیم می‌کند. ولی نکته‌ی خنده دار اینجا بود که خط اول را پایین می‌اندازد و خط دوم را بالا. برای همین این تکه کد را نوشتم و به طور جداگانه در گیت هاب هم قرار داده‌ام.
 
این تکه کد را هم بعد از
//1.remove tags
                 text = CleanScriptTags(text);
 به برنامه اضافه می‌کنیم:
  text =StringUtils.ConvertToMultiLine(text);
از این پس خطوط به طولی بین 30 کاراکتر تا چهل کاراکتر  شکسته خواهند شد و مشکل خطوط بلند هم نخواهیم داشت.
کد متد ConvertToMultiline:
namespace Utils
{
    public static class StringUtils
    {
        public static string ConvertToMultiLine(String text, int min = 30, int max = 40)
        {
            if (text.Trim() == "")
                return text;

            string[] words = text.Split(new string[] { " " }, StringSplitOptions.None);

            string text1 = "";
            string text2 = "";
            foreach (string w in words)
            {
                if (text1.Length < min)
                {
                    if (text1.Length == 0)
                    {
                        text1 = w;
                        continue;
                    }

                    if (w.Length + text1.Length <= max)
                        text1 += " " + w;
                }
                else
                    text2 += w + " ";

            }
            text1 = text1.Trim();
            text2 = text2.Trim();
            if (text2.Length > 0)
            {
                text1 += Environment.NewLine + ConvertToMultiLine(text2, min, max);
            }
            return text1;
        }
      
    }
}
آرگومان‌های min و max که به طور پیش فرض 30 و 40 هستند، سعی می‌کنند که هر خط را در نهایت به طور حدودی بین 30 تا 40 کاراکتر نگه دارند.
نکته پایانی : خوشحال میشم دوستان در این پروژه مشارکت داشته باشند و اگر جایی نیاز به اصلاح، بهبود یا ایجاد امکانی جدید دارد  کمک حال باشند و سعی کنند تا آنجا که می‌شود برنامه را روی net frame work 2. نگه دارند و بالاتر نبرند. چون استفاده کننده‌های این برنامه کاربران عادی و گاها با دانش پایین هستند و خیلی از آن‌ها هنوز از ویندوز xp استفاده می‌کنند تا در اجرای برنامه خیلی دچار مشکل نشده و راحت برای بسیاری از آن‌ها اجرا شود.

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


ایجاد یک برنامه‌ی خالی React برای آزمایش توابع Redux

در اینجا برای بررسی توابع Redux، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> npm i -g create-react-app
> create-react-app state-management-redux-mobx
> cd state-management-redux-mobx
> npm start
در ادامه کتابخانه‌ی Redux را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save redux
البته برای کار با Redux، الزاما نیازی به طی مراحل فوق نیست؛ ولی چون این قالب، یک محیط آماده‌ی کار با ES6 را فراهم می‌کند، به سادگی می‌توان فایل index.js آن‌را خالی کرد و سپس شروع به کدنویسی و آزمایش Redux نمود.


معرفی سریع توابع Redux

Redux، کتابخانه‌ی کوچکی است و تنها از 5 تابع تشکیل شده‌است:
applyMiddleware: function()
bindActionCreators: function()
combineReducers: function()
compose: function()
createStore: function()
و سه مورد از آن‌ها بیشتر کمکی هستند. برای مثال تابع compose مانند متد flow و یا pipe در کتابخانه‌ی lodash است و اصلا به Redux مرتبط نیست. تابع combineReducers، اشیاء موجود در state را با هم ترکیب می‌کند. bindActionCreators نیز یک تابع کمکی است جهت ایجاد ساده‌تر ActionCreators. بنابراین کتابخانه‌ی Redux، آنچنان گسترده نیست.


بررسی تابع compose با یک مثال

پس از ایجاد پروژه‌ی React و افزودن کتابخانه‌ی Redux به آن، به فایل src\index.js این پروژه مراجعه کرده و محتویات آن‌را با قطعه کد ذیل، تعویض می‌کنیم:
import { compose } from "redux";

const makeLouder = text => text.toUpperCase();
const repeatThreeTimes = text => text.repeat(3);
const embolden = text => text.bold();

const makeLouderAndRepeatThreeTimesAndEmbolden = compose(
  embolden,
  repeatThreeTimes,
  makeLouder
);
console.log(makeLouderAndRepeatThreeTimesAndEmbolden("Hello"));
- در ابتدای این ماژول، تابع compose را از کتابخانه‌ی redux دریافت کرده‌ایم.
- سپس سه تابع ساده را برای ضخیم کردن، تکرار و با حروف بزرگ نمایش دادن یک متن ورودی، تعریف کرده‌ایم.
- اکنون با استفاده از متد compose کتابخانه‌ی redux، این سه متد را به صورت ترکیبی، بر روی متن ورودی Hello، اعمال کرده‌ایم.
- در آخر اگر برای مشاهده‌ی نتیجه‌ی اجرای console.log بر روی آن، به کنسول توسعه دهندگان مرورگر مراجعه کنیم، به خروجی زیر خواهیم رسید:
 <b>HELLOHELLOHELLO</b>
همانطور که مشاهده می‌کنید، متن Hello را سه بار با حروف بزرگ تکرار نموده و در نهایت آن‌را با تک b محصور کرده‌است.


بررسی تابع createStore با یک مثال

Store در Redux، جائی است که در آن state برنامه ذخیره می‌شود. تابع createStore که کار ایجاد store را انجام می‌دهد، یک پارامتر را دریافت می‌کند و آن‌هم تابع reducer است که در قسمت قبل معرفی شد و در ساده‌ترین حالت آن، به نحو زیر با یک متد خالی، قابل فراخوانی است:
import { createStore } from "redux";

createStore(() => {});
تابع reducer دو پارامتر را دریافت می‌کند: وضعیت فعلی برنامه (state در اینجا) و رخ‌دادی که واقع شده‌است (action در اینجا). خروجی آن نیز یک state جدید بر این اساس است:
import { createStore } from "redux";

const reducer = (state, action) => {
  return state;
};
const store = createStore(reducer);
console.log(store);
در این مثال، تابع reducer را طوری تعریف کرده‌ایم که بر اساس هر نوع رخ‌دادی که به آن برسد، همان وضعیت قبلی را بازگشت دهد. سپس بر اساس آن یک store را ایجاد کرده‌ایم. اگر به خروجی console.log بررسی محتوای این شیء دقت کنیم، به صورت زیر خواهد بود:


در شیء store، چهار متد dispatch, subscribe, getState, replaceReducer مشخص هستند:
- کارکرد متد replaceReducer مشخص است؛ یک reducer جدید را به آن می‌دهیم و reducer قبلی را جایگزین می‌کند.
- متد dispatch آن مرتبط است به مبحث dispatch actions که در قسمت قبل به آن پرداختیم. هدف آن تغییر state، بر اساس یک action رسیده‌است.
- متد getState، وضعیت فعلی state را باز می‌گرداند.
- متد subscribe با هر تغییری در state، سبب بروز رخ‌دادی می‌شود. یکی از کاربردهای آن می‌تواند به روز رسانی UI، بر اساس تغییرات state باشد. برای مثال کتابخانه‌ی دیگری به نام react redux، دقیقا همین کار را به کمک متد subscribe، انجام می‌دهد. در این قسمت، هدف ما بررسی پشت صحنه‌ی کتابخانه‌هایی مانند react redux است که چه متدهایی را محصور کرده‌اند و دقیقا چگونه کار می‌کنند.

در مثال زیر، مقدار ابتدایی پارامتر state متد reducer را به یک شیء که دارای خاصیت value و مقدار 1 است، تنظیم کرده‌ایم:
import { createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  return state;
};
const store = createStore(reducer);
console.log(store.getState());
سپس بر این اساس، store را ایجاد کرده و متد getState آن‌را فراخوانی کرده‌ایم. خروجی آن به صورت زیر است، که معادل وضعیت فعلی state در store می‌باشد:


در کامپوننت‌های React، این نوع خواص به صورت props ارسال می‌شوند. کل state در Redux ذخیره شده و سپس قابل دسترسی و خواندن خواهد شد.


بررسی متد dispatch یک store با مثال

در اینجا مثالی را از کاربرد متد dispatch ملاحظه می‌کنید که یک شیء را می‌پذیرد:
import { compose, createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  console.log("Something happened!", action);
  return state;
};
const store = createStore(reducer);
console.log(store.getState());
store.dispatch({ type: "ADD" });
متد reducer با یک state ابتدایی تنظیم شده‌ی به شیء { value: 1 } تعریف شده و سپس با ارسال آن به createStore و ایجاد store، اکنون می‌توانیم رخ‌دادهایی را به آن dispatch کنیم.
فرمت شیءای که به متد dispatch ارسال می‌شود، حتما باید به همراه خاصیت type باشد؛ در غیر اینصورت با صدور استثنائی، این نکته را گوشزد می‌کند. مقدار آن نیز یک رشته‌است که بیانگر وقوع رخدادی در برنامه می‌باشد؛ برای مثال کاربر درخواست دریافت اطلاعاتی را کرده‌است و یا کاربر از سیستم خارج شده‌است و امثال آن.

خروجی قطعه کد فوق، به صورت زیر است:


با اجرای برنامه، یک رخ‌داد درونی از نوع redux/INITo.2.g.b.y.i@@ صادر شده‌است که هدف آن آغاز و مقدار دهی اولیه‌ی store است. پس از آن زمانیکه متد store.dispatch فراخوانی شده‌است، مجددا console.log داخل متد reducer فراخوانی شده‌است که اینبار به همراه type ای است که ما مشخص کرده‌ایم.

در حالت کلی، اینکه شیء ارسالی توسط dispatch را چگونه طراحی می‌کنید، اختیاری است؛ اما الگوی پیشنهادی در این زمینه، redux standard actions نام دارد و به صورت زیر است که هدف از آن، یک‌دست شدن طراحی و پیاده سازی برنامه است:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
- نوع action باید همواره قید شود و عموما رشته‌ای است.
- سپس به خاصیت payload، تمام داده‌های مرتبط با آن اکشن، مانند نتیجه‌ی بازگشتی از یک API، به صورت یک شیء، انتساب داده می‌شود.
- خاصیت meta، مرتبط با متادیتا و اطلاعات اضافی است که عموما استفاده نمی‌شود.

اکنون که طراحی شیء ارسالی به پارامتر action متد reducer مشخص شد، می‌توان بر اساس خاصیت type آن، یک switch را طراحی کرد و نسبت به نوع‌های مختلف رسیده، واکنش نشان داد:
import { createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  console.log("Something happened!", action);

  if (action.type === "ADD") {
    const value = state.value;
    const amount = action.payload.amount;
    state.value = value + amount;
  }

  return state;
};
const store = createStore(reducer);
const firstState = store.getState();
console.log(firstState);
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
const secondState = store.getState();
console.log(secondState);
console.log("secondState === firstState", secondState === firstState);
در اینجا اکشن از نوع ADD، با یک payload با محتوای شیءای که دارای خاصیت amount با مقدار 2 است، به متد reducer مربوط به store، ارسال می‌شود. سپس در متد reducer، بر اساس مقدار action.type، تصمیم‌گیری خواهد شد. اگر این مقدار مساوی اکشن خاص ADD بود، آنگاه مقدار value شیء state را با مقدار amount جمع زده و به state.value انتساب می‌دهیم. اکنون پس از فراخوانی متد store.dispatch، اگر خروجی متد ()store.getState را بررسی کنیم، به صورت {value: 3} در آمده‌است ... و این کاری است که نباید انجام شود! نباید مقدار شیء state را به صورت مستقیم تغییر داد. چون در آخرین بررسی صورت گرفته که کار مقایسه‌ی بین state پیش و پس از فراخوانی store.dispatch است (secondState === firstState)، حاصل true است. یعنی اگر با این روش با React کار کنیم، نمی‌تواند محاسبه کند که چه چیزی در state تغییر کرده‌است، تا بر اساس آن UI را به روز رسانی کند.

یک روش حل این مشکل، حذف سطر state.value = value + amount و جایگزینی آن با یک return است که شیء state جدیدی را بازگشت می‌دهد:
return {
  value: value + amount
};
اکنون نتیجه‌ی مقایسه‌ی secondState === firstState دیگر true نخواهد بود.


بررسی متد subscribe یک store با مثال

در ادامه به store همان مثال فوق، متد subscribe را نیز اضافه می‌کنیم و سپس دوبار، کار store.dispatch را انجام خواهیم داد:
const store = createStore(reducer);

const unsubscribe = store.subscribe(() => console.log(store.getState()));

store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });

unsubscribe();
- هر بار که متد store.dispatch فراخوانی می‌شود، یکبار callback function ارسالی به متد store.subscribe، فراخوانی خواهد شد که در اینجا کار نمایش مقدار شیء state را به عهده دارد. در یک چنین حالتی مشترکین به store، متوجه خواهند شد که احتمالا state جدیدی توسط متد reducer بازگشت داده شده‌است و سپس بر اساس آن برای مثال تصمیم خواهند گرفت تا UI را به روز رسانی کنند.
- خروجی مستقیم متد store.subscribe نیز یک متد است که unsubscribe نام دارد و می‌توان در پایان کار، برای جلوگیری از نشتی‌های حافظه، آن‌را فراخوانی کرد.

خروجی حاصل از console.log منتسب به متد store.subscribe، با دوبار فراخوانی متد store.dispatch پس از آن، به صورت زیر است:
{value: 3}
{value: 5}


بررسی تابع combineReducers با یک مثال

اگر قرار باشد در متد reducer، صدها if action.type را تعریف کرد ... پس از مدتی از کنترل خارج می‌شود و غیرقابل نگهداری خواهد شد. تابع combineReducers به همین جهت طراحی شده‌است تا چندین متد مجزای reducer را با هم ترکیب کند. بنابراین می‌توان در برنامه صدها متد کوچک reducer را که قابلیت توسعه و نگهداری بالایی را دارند، پیاده سازی کرد و سپس با استفاده از تابع combineReducers، آن‌ها را یکی کرده و به متد createStore ارسال کرد. نکته‌ی مهم اینجا است که هرچند اینبار می‌توان تعداد زیادی reducer را طراحی کرد، اما توسط تابع combineReducers، مقدار action ارسالی، از تمام این reducerها عبور داده خواهد شد. به این ترتیب اگر نیاز بود می‌توان به یک action، در چندین متد مختلف reducer گوش فرا داد و بر اساس آن تصمیم گیری کرد. بنابراین بهتر است هر reducer تعریف شده در صورتیکه action.type آن با action رسیده تطابق نداشته باشد، همان state قبلی را بازگشت دهد تا بتوان زنجیره‌ی reducerها را تشکیل داد و بهتر مدیریت کرد.
برای نمونه اگر متد reducer فعلی را به calculatorReducer تغییر نام دهیم، روش معرفی آن توسط متد combineReducers به متد createStore به صورت زیر است:
import { combineReducers, createStore } from "redux";
  // ...

const calculatorReducer = (state = { value: 1 }, action) => {
  // ...
};
const store = createStore(
  combineReducers({
    calculator: calculatorReducer
  })
);
به شیء ارسالی به متد combineReducers، هر reducer موجود به صورت یک خاصیت جدید اضافه می‌شود.


بررسی تابع bindActionCreators با یک مثال

فرض کنید می‌خواهیم متد dispatch را چندین بار فراخوانی کنیم:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
بجای اینکه شیء ارسالی به آن‌را به این صورت ایجاد کنیم، می‌توان یک تابع را برای آن ایجاد کرد تا مقدار amount را گرفته و شیء action مدنظر را بازگشت دهد:
const createAddAction = amount => {
  return {
    type: "ADD",
    payload: {
      amount // = amount: amount
    },
    meta: {}
  };
};
و سپس می‌توان آن‌را به صورت زیر مورد استفاده قرار داد:
store.dispatch(createAddAction(2));
store.dispatch(createAddAction(2));
و یا توسط تابع bindActionCreators، می‌توان فراخوانی فوق را به صورت زیر نیز انجام داد و نتیجه یکی است:
import { bindActionCreators, combineReducers, compose, createStore } from "redux";

// ...
const dispatchAdd = bindActionCreators(createAddAction, store.dispatch);
dispatchAdd(2);
dispatchAdd(2);
همانطور که ملاحظه می‌کنید، bindActionCreators فقط یک تابع کمکی است تا کار dispatch action را ساده کند.


میان‌افزارها (Middlewares) در Redux

پس از اینکه یک اکشن، به سمت reducer ارسال شد و پیش از رسیدن آن به reducer، می‌توان کدهای دیگری را نیز اجرا کرد. برای مثال چون این توابع خالص هستند، نمی‌توان اعمالی را داخل آن‌ها اجرا کرد که به همراه اثرات جانبی مانند کار با یک API خارجی باشند. با استفاده از میان‌افزارها در این بین می‌توان کدهایی را که با دنیای خارج تعامل دارند نیز اجرا کرد.
یک میان‌افزار در Redux، همانند سایر قسمت‌های آن، تنها یک تابع ساده‌ی جاوا اسکریپتی است:
const logger = ({ getState }) => {
  return next => action => {
    console.log(
      'MIDDLEWARE',
      getState(),
      action
    );
    const value = next(action);
    console.log({value});
    return value;
  }
}
این تابع میان‌افزار، امکان دریافت وضعیت فعلی store را دارد و در نهایت یک تابع دیگر را باز می‌گرداند. داخل این تابع بازگشت داده شده می‌توان اعمال دارای اثرات جانبی را نیز اجرا کرد؛ مانند فراخوانی console.log و یا صدور یک درخواست Ajax ای. یکی دیگر از کاربردهای میان‌افزارها، انجام کارهای آماری بر روی اکشن‌های ارسالی است. بجای اینکه کدهای متناظر را داخل  reducerها قرار داد، می‌توان به رسیدن اکشن‌های خاصی در این بین گوش فرا داد و برای مثال آن‌ها را لاگ کرد و یا آماری را از آن‌ها تهیه نمود.
در پایان کار میان‌افزار باید متد next آن‌را فراخوانی کرد تا بتوان میان‌افزارهای متعددی را زنجیروار اجرا کرد.
در آخر برای معرفی آن به یک store می‌توان از تابع applyMiddleware استفاده کرد:
import { applyMiddleware, bindActionCreators, combineReducers, compose, createStore } from "redux";

// ...

const store = createStore(
  combineReducers({
    calculator: calculatorReducer
  }),
  applyMiddleware(logger)
);

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: state-management-redux-mobx-part02.zip
نظرات مطالب
پیاده سازی JSON Web Token با ASP.NET Web API 2.x
با سلام؛ درصورت امکان برای 2 مورد زیر راهنمائی کنید:
1. در یک پروژه آنگولار 5 برای نقش (Role) کاربر یک چنین سناریویی وجود دارد: ابتدا کاربر لاگین میکند، نقش‌های متعدد کاربر نمایش داده می‌شود سپس کاربر با یکی از نقش‌ها وارد سیستم می‌شود.این نقش باید به AccessToken ضمیمه شود تا در JwtAttribute سمت سرور بتوان نقش کاربر را بررسی کرد. 
بنده الان چنین کاری انجام داده ام: کاربر وارد سیستم می‌شود، درصورت ورود موفق به صفحه انتخاب نقش هدایت می‌شود. پس از انتخاب نقش متد RefreshToken رو به همراه نقش انتخابی فراخوانی میکنم و با استفاده از کد زیر درسمت سرور نقش را به توکن اضافه میکنم. 
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
var roleID = form.Result["roleID"];
newIdentity.AddClaim(new Claim("roleID", roleID));
newIdentity.AddClaim(new Claim("newClaim", "refreshToken"));

newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
2. در چنین سناریویی عنوان نقش (که طولانی هم هست) برای نمایش در بالای صفحه کجا باید ذخیره شود؟به نظرم اگر در AccessToken ذخیره شود باعث افزایش طول توکن می‌شود که در رفرش توکن‌های بعدی حجم توکن بالا میرود
با تشکر
مطالب
ایجاد چارت سازمانی تحت وب #1
برای ایجاد چارت سازمانی همواره از راههای دیگر استفاده میکردیم مثلا از تصویر و فایل PDF ، فلش و یا ...
این مورد همیشه باعث اذیت طراحان وب و برنامه نویسان تحت وب بود. با ظهور برخی امکانات در HTML5 که میتوانید توضیحات آن را در مطلب Canvas Reference - قسمت اول سایت جاری مطالعه نمائید ، هم اکنون با استفاده از این امکانات و کتابخانه مربوط به ساخت نمودار سازمانی میتوانید مانند شکل ذیل نمودار در وب ایجاد نمائید.

نمودار سازمانی

برای اینکار ابتدا یک صفحه index.html ایجاد نموده و در قسمت body آن یک المنت از نوع canvas ایجاد نمائید:

<canvas id="canvas1" width="800" height="800" ></canvas>
سپس فایلهای مورد نیاز را در قسمت head آن ارجاع نمائید:
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" >
<!--[if ie]><script type="text/javascript" src="excanvas.js"></script><![endif]-->
<script type="text/javascript" src="orgchart.js"></script>
حال در انتهای متن فایل html این کد را قرار دهید :
var o = new orgChart();

o.addNode(1, '', '', 'Root node');
o.addNode(2, 1, 'u', 'u-node 1');
o.addNode(3, 1, 'u', 'u-node 2');
o.addNode(4, 1, 'u', 'u-node 3');
o.addNode(5, 1, 'l', 'l-node 1');
o.addNode(6, 1, 'l', 'l-node 2');
o.addNode(7, 1, 'r', 'r-node 1');
o.addNode(8, 1, 'r', 'r-node 2');
o.addNode(9, 1, 'r', 'r-node 3');

o.addNode('', '', '', 'Root 2');
o.addNode('', 'Root 2', 'r', 'using');
o.addNode('', 'Root 2', 'r', 'text as\nid');

o.drawChart('canvas1');
انواع نود در نمودار :
u -> نودهایی که در یک سطح افقی (کنار هم ) و در سطح زیرین یک پدر قرار میگیرند.
l -> نودهایی که در یک سطح عمودی ( زیر هم ) و در سمت چپ نود پدر قرار میگیرند.
r -> نوهایی که در یک سطح عمودی ( زیر هم ) و در سمت راست نود پدر قرار میگیرند.

پارامترهای نودها :
  1. شناسه نود است و میتواند عدد یا رشته باشد . در صورتی که خالی بماند متن نود بعنوان شناسه استفاده میشود.
  2. شناسه نود پدر است و در صورتی که خالی ( یا ناشناس ) باشد بعنوان نود اصلی ( پدر ) نمایش داده میشود.
  3. نوع اتصال نودهای r , l , u . برای نود پدر نادیده گرفته میشود.
  4. متن نود . استفاده از "n\"  جلمه را شکسته و به خط جدید منتقل میکند.
  5. نود با نشانه مشخص ( اگر 1 باشد ، حاشیه با ضخامت کشیده میشود. ( اختیاری )
  6. آدرس یک نود - اختصاص آدرس url  جهت اضافه کردن آن به یک نود ( اختیاری )
  7. رنگ خط حاشیه ( اختیاری )
  8. رنگ پس زمینه ( اختیاری )
  9. رنگ متن و فونت ( اختیاری )

پارامترهای تابع ()drowChart :

  1. شناسه المنت canvas
  2. تراز کردن نمودار با استفاده از حرف 'c' یا کلمه 'center' . در صورت حذف ، نمودار در سمت چپ صفحه نمایش داده خواهد شد. تراز چپ ( اختیاری )
  3. تناسب اندازه canvas .  اگر true باشد canvas به اندازه نمودار چارت ، تغییر اندازه میدهد . (اختیاری)
مطمئن باشید که فایل index.html , excanvas.js  ,orgchart.js  در یک فولدر قرار گرفته باشند .
در مطلب بعدی نمایش با رنگهای مختلف ارائه خواهد شد.

مورد نیازهای مطلب جاری :
  1. excanvas.js
  2. orgchart.js
  3. index.html
مطالب
آشنایی با Saltarelle کامپایلر قدرتمند #C به جاوااسکریپت

شاید ساده‌ترین تعریف برای  Saltarelle  این باشد که «کامپایلریست که کد‌های C# را به جاوا اسکریپت تبدیل می‌کند». محاسن زیادی را می‌توان برای اینگونه کامپایلر‌ها نام برد؛ مخصوصا در پروژه‌های سازمانی که نگهداری از کد‌های جاوا اسکریپت بسیار سخت و گاهی خارج از توان است و این شاید مهمترین عامل ظهور ابزارهای جدید از قبیل Typescript باشد.

در هر صورت اگر حوصله و وقت کافی برای تجهیز تیم نرم افزاری، به دانش یک زبان جدید مانند Typescript نباشد، استفاده از توان و دانش تیم تولید، از زبان C# ساده‌ترین راه حل است و اگر ابزاری مطمئن برای استفاده از حداکثر قدرت JavaScript همراه با امکانات نگهداری و توسعه کد‌ها وجود داشته باشد، بی شک Saltarelle یکی از بهترین‌های آنهاست.

قبلا کامپایلر هایی از این دست مانند  Script# وجود داشتند، اما فاقد همه امکانات C# بوده وعملا قدرت کامل C# در کد نویسی وجود نداشت. اما با توجه به ادعای توسعه دهندگان این کامپایلر سورس باز در استفاده‌ی حداکثری از کلیه ویژگی‌های C# 5 و با وجود Library ‌های متعدد می‌توان Saltarelle  را عملا یک کامپایلر موفق در این زمینه دانست.

برای استفاده از Saltarelle در یک برنامه وب ساده باید یک پروژه Console Application به Solution اضافه کرد و پکیج Saltarelle.Compiler را از nuget نصب نمایید. بعد از نصب این پکیج، کلیه Reference ‌ها از پروژه حدف می‌شوند و هر بار Build توسط کامپایلر Saltarelle  انجام می‌شود. البته با اولین Build، مقداری Error را خواهید دید که برای از بین بردنشان نیاز است پکیج Saltarelle.Runtime را نیز در این پروژه نصب نمایید:

PM> Install-Package Saltarelle.Compiler
PM> Install-Package Saltarelle.Runtime

در صورتیکه کماکان Build  نهایی با Error همرا بود، یکبار این پروژه را Unload  و سپس مجددا Load نمایید



UI یک پروژه وب MVC است و Client یک Console Application که پکیج‌های مورد نیاز Saltarelle  روی آن نصب شده است.

در صورتیکه پروژه را Build نماییم و نگاهی به پوشه‌ی Debug بیاندازیم، یک فایل JavaScript همنام پروژه وجود دارد:


برای اینکه بعد از هر بار Build ، فایل اسکریپت به پوشه‌ی مربوطه در پروژه UI منتقل شود کافیست کد زیر را در Post Build  پروژه Client بنویسیم: 

copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)SalratelleSample.UI\Scripts"

اکنون پس از هر بار Build ، فایل اسکریپت مورد نظر در پوشه‌ی Scripts پروژه UI  آپدیت می‌شود:


در ادامه کافیست فایل اسکریپت را به layout اضافه کنیم. 

<script src="~/Scripts/SaltarelleSample.Client.js"></script>

در پوشه‌ی Saltarelle.Runtime در پکیج‌های نصب شده، یک فایل اسکریپت به نام mscorlib.min.js نیز وجود دارد که حاوی اسکریپت‌های مورد نیاز Saltarelle در هنگام اجراست. آن را به پوشه اسکریپت‌های پروژه UI کپی نمایید و سپس به Layout  اضافه کنید. 

<script src="~/Scripts/mscorlib.min.js"></script>
<script src="~/Scripts/SaltarelleSample.Client.js"></script>

حال نوبت به اضافه نمودن library‌های مورد نیازمان است. برای دسترسی به آبجکت هایی از قبیل document, window, element و غیره در جاوااسکریپت می‌توان پکیج Saltarelle.Web را در پروژه‌ی Client نصب نمود و برای دسترسی به اشیاء و فرمانهای jQuery، پکیج Salratelle.jQuery را نصب نمایید. 

> Install-Package Saltarelle.Web
> Install-Package Saltarelle.jQuery

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

به طور ساده‌تر وقتی از jQuery library استفاده می‌کنید هیچ فایل اسکریپت اضافه‌ای تولید نمی‌شود، اما باید اسکریپت jQuery به صفحه شما اضافه شده باشد.

<script src="~/Scripts/jquery-1.10.2.min.js"></script>

مثال ما یک اپلیکیشن ساده برای خواندن فید‌های همین سایت است. ابتدا کد‌های سمت سرور را در پروژه UI  می نویسیم.

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

public class Feed
    {
        public string FeedId { get; set; }
        public string Title { get; set; }
        public string Address { get; set; }

    }
    public class Item
    {
        public string Title { get; set; }
        public string Link { get; set; }
        public string Description { get; set; }
    }

و یک کلاس برای مدیریت منطق برنامه 

 public class SiteManager
    {
        private static List<Feed> _feeds;
        public static List<Feed> Feeds
        {
            get
            {
                if (_feeds == null)
                    _feeds = CreateSites();
                return _feeds;
            }
        }
        private static List<Feed> CreateSites()
        {
            return new List<Feed>() { 
                new Feed(){
                    FeedId = "1",
                    Title = "آخرین تغییرات سایت",
                    Address = "https://www.dntips.ir/rss.xml"
                },
                 new Feed(){
                    FeedId = "2",
                    Title = "مطالب سایت",
                    Address = "https://www.dntips.ir/feeds/posts"
                },
                 new Feed(){
                    FeedId = "3",
                    Title = "نظرات سایت",
                    Address = "https://www.dntips.ir/feeds/comments"
                },
                 new Feed(){
                    FeedId = "4",
                    Title = "خلاصه اشتراک ها",
                    Address = "https://www.dntips.ir/feed/news"
                },
            };
        }

        public static IEnumerable<Item> GetNews(string id)
        {
            XDocument feedXML = XDocument.Load(Feeds.Find(s=> s.FeedId == id).Address);
            var feeds = from feed in feedXML.Descendants("item")
                        select new Item
                        {
                            Title = feed.Element("title").Value,
                            Link = feed.Element("link").Value,
                            Description = feed.Element("description").Value
                        };
            return feeds;
        }

    }

کلاس SiteManager فقط یک لیست از فید‌ها دارد و متدی که با گرفتن شناسه‌ی فید ، یک لیست از آیتم‌های موجود در آن فید ایجاد می‌کند.

حال دو ApiController برای دریافت داده‌ها ایجاد می‌کنیم

public class FeedController : ApiController  
{
        // GET api/<controller>
        public IEnumerable<Feed> Get()
        {
            return SiteManager.Feeds;
        }
    }

public class ItemsController : ApiController
    {
        // GET api/<controller>/5
        public IEnumerable<Item> Get(string id)
        {
            return SiteManager.GetNews(id);
        }
    }

در View پیش‌فرض که Index از کنترلر Home  است،  یک Html ساده برای فرم  صفحه اضافه می‌کنیم 

<div>
    <div>
        <h2>Feeds</h2>
        <ul id="Feeds">
           
        </ul>
    </div>
    <div>
        <h2>Items</h2>
        <p id="FeedItems">
        </p>
    </div>
   
</div>

در المنت Feeds لیست فید‌ها را قرار می‌دهیم و در FeedItems آیتم‌های مربوط به هر فید. حال به سراغ کد‌های سمت کلاینت می‌رویم و به جای جاوا اسکریپت از Saltarelle استفاده می‌کنیم.

کلاس Program را از پروژه Client باز می‌کنیم و متد Main را به شکل زیر تغییر می‌دهیم:

static void Main()
        {
            jQuery.OnDocumentReady(() => {
                FillFeeds();
            });
        }

بعد از کامپایل شدن، کد #C شارپ بالا به صورت زیر در می‌آید: 

$SaltarelleSample_Client_$Program.$main = function() {
$(function() {
$SaltarelleSample_Client_$Program.$fillFeeds();
});
};
$SaltarelleSample_Client_$Program.$main();

و این همان متد معروف jQuery است که Saltarelle.jQuery برایمان ایجاد کرده است.

متد FillFeeds را به شکل زیر پیاده سازی می‌کنیم

private static void FillFeeds()
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/feed",
                Type = "GET",
                Success = (d,t,r) => {

                    // Fill 
                    var ul = jQuery.Select("#Feeds");
                    jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });
                }
            });
        }

آبجکت jQuery، متدی به نام Ajax دارد که یک شی از کلاس jQueryAjaxOptions را به عنوان پارامتر می‌پذیرد. این کلاس کلیه خصوصیات متد Ajax در jQuery را پیاده سازی می‌کند. نکته شیرین آن توانایی نوشتن lambda برای Delegate هاست.

خاصیت Success یک Delegate است که 3 پارامتر ورودی را می‌پذیرد.

public delegate void AjaxRequestCallback(object data, string textStatus, jQueryXmlHttpRequest request);

data همان مقداریست که api باز می‌گرداند که یک لیست از Feed هاست. برای زیبایی کار، من یک کلاس Feed در پروژه Client اضافه می‌کنم که خصوصیاتی مشترک با کلاس اصلی سمت سرور دارد و مقدار برگشی Ajax را به آن تبدیل می‌کنم.

کلاس Feed و Item

 [PreserveMemberCase()]
    public class Feed
    {
        //[ScriptName("FeedId")]
        public string FeedId;

        //[ScriptName("Title")]
        public string Title;

        //[ScriptName("Address")]
        public string Address;

    }

    [PreserveMemberCase()]
    public class Item
    {
        // [ScriptName("Title")]
        public string Title;

        // [ScriptName("Link")]
        public string Link;

        // [ScriptName("Description")]
        public string Description;
    }
Attrubute‌های زیادی در Saltarelle وجود دارند و از آنجایی که کامپایلر اسم فیلد‌ها را camelCase کامپایل می‌کند من برای جلوگیری از آن از PreserveMemberCase  بر روی هر کلاس استفاده کردم. می‌توانید اسم هر فیلد را سفارشی کامپایل نمایید. 
jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });

به ازای هر آیتمی که در شیء بازگشتی وجود دارد، با استفاد از متد each در jQuery یک li ایجاد می‌کنیم. همان طور که می‌بینید کلیه خواص، به شکل Fluent قابل اضافه شدن می‌باشد. سپس برای li یک رویداد کلیک که در صورت وقوع، متد FillData را با شناسه فید کلیک شده فراخوانی می‌کند و در آخر li را به المنت ul اضافه می‌کنیم.

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

private static void FillData(string p)
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/items/" + p,
                Type = "GET",
                Success = (d, t, r) => {
                    var content = jQuery.Select("#FeedItems");
                    content.Html("");
                    foreach (var item in (List<Item>)d)
                    {
                        var row = jQuery.Select("<div>").AddClass("row").CSS("direction", "rtl");
                        var link = jQuery.Select("<a>").Attribute("href", item.Link).Text(item.Title);
                        row.Append(link);
                        content.Append(row);
                    }
                }
            });
        }
خروجی برنامه به شکل زیر است: 

در این مثال ما از Saltarelle.jQuery برای استفاده از jQuery.js استفاده نمودیم. library‌های متعددی برای Saltarelle  از قبیل  linq,angular,knockout,jQueryUI,nodeJs ایجاد شده و همچنین قابلیت‌های زیادی برای نوشتن imported library‌های سفارشی نیز وجود دارد. 

مطمئنا استفاده از چنین کامپایلرهایی راه حلی سریع برای رهایی از مشکلات متعدد کد نویسی با جاوا اسکریپت در نرم افزارهای بزرگ مقیاس است. اما مقایسه آنها با ابزارهایی از قبیل typescript احتیاج به زمان و تجربه کافی در این زمینه دارد.

مطالب
Garbage Collector در #C - قسمت دوم
در این مطلب قصد داریم به تفاوت‎های بین Stack و Heap در Memory و زبان #C بپردازیم.

به زبان ساده، وقتی شما متغیر جدیدی را ایجاد میکنید، با توجه به نوع (Type) آن متغیر، "مقدار" متغیر شما در Stack یا Heap قرار خواهد گرفت.

Stack

Stack نوعی ساختمان داده‌است که در آن، داده‌ها بصورت خطی قرار گرفته و اصطلاحا ساختار LIFO ( مخفف Last in, First Out ) دارند، بدین معنا که همیشه آخرین داده‌ای که داخل Stack قرار داده‌اید، اولین داده‌ای است که قادر به خواندن آن خواهید بود. وقتی در ساختار Stack داده‌ای را قرار میدهیم، اصطلاحا آن را Push کرده و وقتی میخواهیم آخرین داده را با توجه به ساختار خطی آن بخوانیم، داده را Pop میکنیم.


این ساختمان داده، داخل Memory پیاده سازی شده است و تعدادی از متغیرهایی را که ما داخل کد ایجاد میکنیم، در این نوع ساختمان داده از Memory نگهداری میشوند.

شرط قرار گرفتن مقدار یک متغیر داخل Stack این است که متغیر از نوع Value Type باشد. در زبان #C، بطور کلی Struct و Enum‌ها Value Type هستند و بصورت پیشفرض داخل Stack قرار میگیرند. تمامی ValueType‌ها در #C، بطور implicit از System.ValueType ارث بری میکنند.

Type‌های زیر، Value Type‌های پیشفرض تعریف شده‌ی در زبان #C هستند که به آن‌ها Simple Type نیز گفته میشوند:


Represents   Type
 Boolean value  bool
8-bit unsigned integer
 byte
 16-bit Unicode character  char
128-bit precise decimal values with 28-29 significant digits   decimal
 64-bit double-precision floating point type  double
 32-bit single-precision floating point type  float
 32-bit signed integer type  int
 64-bit signed integer type  long
 8-bit signed integer type  sbyte
 16-bit signed integer type  short
 32-bit unsigned integer type  uint
 64-bit unsigned integer type  ulong
16-bit unsigned integer type   ushort


اگر سورس هرکدام از این تایپ‌ها مانند  Int32 را در ریپازیتوری CoreFX مایکروسافت بررسی کنید، متوجه خواهید شد که تمامی این تایپ‌ها از نوع Struct تعریف شده‌اند و همانطور که گفتیم، بطور پیش‌فرض، Struct‌ها داخل Stack قرار خواهند گرفت.

طول عمر متغیرهایی که داخل Stack قرار گرفته‌اند، منحصر به پایان اجرای یک متد است. بدین معنا که بعد از به پایان رسیدن یک متد، تمامی متغیرهای مورد استفاده در آن متد، از حافظه Stack بطور خودکار حذف خواهند شد. متغیرهایی که داخل Stack قرار میگیرند، نوع و حجم مقادیرشان بر اساس Type ای دارند، در زمان Compile-Time مشخص است.

متغیرهای محلی (Local Variable ها)، پارامترهای ورودی متد و مقدار بازگشتی یک متد، جز مواردی هستند که مقادیرشان داخل Stack قرار میگیرد:
public static int Add(int number1, int number2)
{
    // number1 is on the stack (function parameter)
    // number2 is on the stack (function parameter)

    int sum = number1 + number2;
    // sum is on the stack (local variable)

    return sum;
}

در زبان #C و در مرحله Compile-Time، کدها به زبان IL (مخفف Intermediate Language) ترجمه میشوند که با نام‌های MSIL (مخفف Microsoft Intermediate Language ) و CIL (مخفف Common Intermediate Language ) نیز، این زبان شناخته میشود. ساختار این زبان Stack-based بوده و با شناخت آن، با مفهوم Stack نیز بهتر میتوانیم آشنا شویم.

IL زبانی است که CLR (مخفف Common Language Runtime) را که همان Runtime مایکروسافت است، شناخته و اجرا میکند. قابل ذکر است که Runtime مایکروسافت Open-Source بوده و سورس آن با نام CoreCLR در گذشته از این آدرس و در حال حاضر با نام Runtime از این آدرس قابل دسترسی است.

با استفاده از برنامه هایی مانند dotPeek یا dnSpy یا ILDASM یا ابزار آنلاینی مانند Sharplab  و ... میتوانید کدهای IL حاصل از dll‌های برنامه خود را ببینید. این ابزارها با یکدیگر تفاوت زیادی ندارند و تنها مزیت dnSpy به نسبت بقیه، قابلیت دیباگ کردن کدهای IL توسط آن میباشد و همچنین ILDASM با نصب Visual Studio، از این مسیر بدون نیاز به نصب برنامه اضافه ای قابل دسترسی است:
C:\Program Files (x86)\Microsoft SDKs\Windows\{version}\Bin\ildasm.exe

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

 

اگر متد Add بالا را با پارامترهای 2 و 5 صدا بزنیم، خروجی IL حاصل از آن، که این دو عدد را بعنوان ورودی گرفته و جمع آنها را بعنوان خروجی میدهد، به این صورت خواهد بود ( قسمت هایی از خروجی جهت سادگی، حذف شده است) :
.method private hidebysig static int32 Add(int32 number1, int32 number2) cil managed
{
  .locals init (int32 V_0, int32 V_1)
  
  IL_0001:  ldarg.0 // Stack is: [2]
  IL_0002:  ldarg.1 // Stack is: [2, 5]
  IL_0003:  add     // Stack is: [7]
  IL_0004:  stloc.0 // Stack is: [] and V_0's value is: 7
  
  IL_0005:  ldloc.0 // Stack is: [7]
  IL_0006:  stloc.1 // Stack is: [] and V_1's value is: 7

  IL_0009:  ldloc.1 // Stack is: [7]
  IL_000a:  ret     // Return [7]
}

میتوانید لیست دستورات مورد استفاده در CIL را از اینجا ببینید.

در ادامه، خط به خط، خروجی حاصل را بررسی میکنیم:

1- در زبان IL، میتوانید مقادیر حاصل از اعمال محاسباتی یا متدهای دیگر را داخل متغیرهای محلی ذخیره کنید، به شرط اینکه آنها را در ابتدا مشخص سازید.
    • با استفاده از locals. که به معنای local variables است، میتوانید متغیرهای مورد نیازتان را در طول عمر این متد، معرفی کنید. دادن نام برای این متغیرها اجباری نیست (V_0 و V_1) و صرفا جهت خوانایی استفاده میشوند.


2- از کلمه کلیدی ldarg (مخفف Load Argument) برای لود کردن آرگومان یا همان پارامتر ورودی متد، داخل Stack استفاده میشود.
    • ldarg.0 به معنای لود کردن پارامتر ورودی اول، داخل Stack است و با فراخوانی آن، Stack Frame دارای یک عضو که مقدار آن 2 است، میشود.
    • ldarg.1 به معنای لود کردن پارامتر ورودی دوم، داخل Stack است و با فراخوانی آن، Stack Frame دارای دو عضو که مقادیر آن 2 و 5 است، میشود.

3- با استفاده از کلمه کلیدی add، مقادیر موجود در Stack با یکدیگر جمع میشوند و Stack Frame دارای یک عضو که مقدار آن 7 است، میشود.

4- با استفاده از کلمه کلیدی stloc (مخفف Store Local)، آخرین عضو موجود در Stack، داخل متغیر محلی ذکر شده، قرار گرفته و ذخیره میشود.
    • stloc.0 به معنای ذخیره سازی آخرین مقدار موجود در Stack یعنی عدد 7، داخل متغیر 0 یعنی همان V_0 میباشد. 

5- با استفاده از کلمه کلیدی ldloc (مخفف Load Local)، میتوان متغیر محلی ذخیره شده را داخل Stack قرار داد.
    • ldloc.0 به معنای Load کردن مقدار ذخیره شده متغیر محلی 0 که همان V_0 است، داخل Stack میباشد.

6- در نهایت، مقدار 7، داخل متغیر 1 یا همان V_1 با دستور stloc.1 بار دیگر ذخیره، با ldloc.1 لود شده و با استفاده از دستور ret، برگشت داده میشود.

* نکته: اگر کدها را بطور دقیق بررسی کرده باشید، احتمالا فکر کرده اید که چه نیازی به ایجاد یک متغیر اضافی و ریختن نتیجه داخل آن و سپس برگشت دادن نتیجه، در مرحله 6 است؟!
در زبان #C، کدهای شما در زمان Release و همچنین JIT-Compilation، طی چندین مرحله Optimize میشوند و یکی از این مراحل، حذف این متغیرهای اضافی جهت Optimization و Performance است؛ پس از این بابت نگرانی وجود ندارد.

* نکته: احتمالا تا به اینجا دلیل بوجود آمدن StackOverflowException را متوجه شده باشید. فضای Stack محدود است. این فضا در سیستم‌های 32 بیت برابر با 1 مگابایت و در سیستم‌های 64 بیت برابر با 4 مگابایت است (Reference). اگر حجم متغیرهایی که روی استک Push میشوند، این محدودیت را رد کنند و یا اگر یک متد بطور دائم خودش را صدا بزند (Recursive) و هیچگاه از آن خارج نشود، با خطای StackOverflowException مواجه میشوید.

Heap


.Heap: a group of things placed, thrown, or lying one on another


در مقابل ساختار ترتیبی و منظم Stack، ساختار Heap قرار دارد. Heap قسمتی از حافظه است که ساختار، ترتیب و Layout خاصی ندارد.
این نوع حافظه بر خلاف Stack، منحصر به یک متد نیست و اصطلاحا Global بوده و در هر قسمتی از برنامه قابل دسترسی است. تخصیص حافظه در این قسمت از حافظه اصطلاحا Dynamic بوده و هر نوع داده ای را در هر زمانی میتوان داخل آن ذخیره کرد.

 string‌ها نمونه‌ای از typeهایی هستند که داخل Heap نگه داری میشوند. دقت کنید وقتی میگوییم نگه داری میشود، منظور «مقدار» یک متغیر است.

وقتی یک متغیر از نوع string را ایجاد میکنیم، مقدار آن داخل Heap و Memory-Address آن متغیر روی Heap، در Stack نگه داری میشود:
public static void SayHi()
{
    string name = "Moien";
}

در این مثال، چون string یک class است، مقدار آن داخل heap ذخیره شده و آدرس آن قسمت (segment) از memory، روی Stack قرار میگیرد:
.method private hidebysig static void SayHi() cil managed
{
  .locals init (string V_0)

  IL_0001:  ldstr      "Moien" // Stack is: [memory-address of string in heap]
  IL_0006:  stloc.0
  
  IL_0007:  ret
}

به متغیرهایی که مقادیرشان داخل Heap ذخیره میشوند، Reference-Type گفته میشود.

* نکته: در این مثال متغیری به نام name ایجاد شده که از آن هیچ استفاده‌ای نشده است. در زمان JIT-Compilation، با توجه با Optimization‌های موجود در سطح CLR، این متد بطور کلی اضافه تشخیص داده شده و از آن صرفنظر خواهد شد.



Boxing and Unboxing


به فرایند تبدیل یک Value-Type مانند int که بصورت پیشفرض داخل Stack ذخیره میشود، به یک object که در داخل Heap ذخیره میشود، Boxing گفته میشود. انجام این عمل باعث allocation بر روی memory میشود که سربار زیادی دارد. 

با انجام عمل Boxing، قادر خواهیم بود تا بعنوان مثال یک عدد را بر خلاف روال عادی آن، روی Heap ذخیره کنیم:
public static void Boxing()
{
    const int number = 5;
    
    object boxedNumber = number;          // implicit boxing using implicit cast
    object boxedNumber = (object)number;  // explicit boxing using direct cast
}

در ابتدا عدد 5 روی Stack ذخیره شده بود، اما با Box کردن آن، یعنی قرار دادن مقدار آن داخل یک object، مقدار از Stack به Heap انتقال داده شده و allocation اتفاق خواهد افتاد:
.method public hidebysig static void Boxing() cil managed
{
  .locals init (object V_0)
  
  IL_0001:  ldc.i4.5                                // Stack is: [5]
  IL_0002:  box        [System.Runtime]System.Int32 // Stack is: [memory-address of 5 in heap]
  
  IL_0007:  stloc.0
  IL_0008:  ret
}

به عکس این عمل، یعنی تبدیل یک Reference-Type به یک Value-Type، اصطلاحا Unboxing گفته میشود:
public static void Unboxing()
{
    object boxedNumber = 5;
    
    int number = (int)boxedNumber;
}

که نتیجه آن، به این صورت خواهد بود:
.method public hidebysig static void Unboxing() cil managed
{
  .locals init (object V_0, int32 V_1)
  
  IL_0001:  ldc.i4.5                                  // Stack is: [5]
  IL_0002:  box        [System.Runtime]System.Int32   // Stack is: [memory-address of 5 in heap]
  IL_0007:  stloc.0                                   // Stack is: []
                                                      
  IL_0008:  ldloc.0                                   // Stack is: [memory-address of 5 in heap]
  IL_0009:  unbox.any  [System.Runtime]System.Int32   // Stack is: [5]
  IL_000e:  stloc.1                                   // Stack is: []
  
  IL_000f:  ret
}

تلاش تیم‌های مایکروسافت طی سال‌های اخیر، باعث افزایش Performance فوق العاده در NET Core. و ASP.NET Core شده است. یکی از دلایل این Performance، جلوگیری بسیار زیاد از allocation در کدهای خود NET. است، که این امر به واسطه اولویت قرار دادن استفاده از Structها میسر گردیده است.

برخلاف Stack که طول عمر متغیرهای موجود در آن، در انتهای یک متد پایان می‌یابند، متغیرهای allocate شده‌ی در Heap به این شکل نبوده و در صورت حذف نکردن آنها بصورت دستی، تا پایان طول عمر اجرای برنامه داخل memory باقی خواهند ماند. اینجا، جاییست که Garbage Collector در NET. وارد عمل میشود.
مطالب
Blazor 5x - قسمت 28 - برنامه‌ی Blazor WASM - نمایش لیست اطلاعات دریافتی از Web API
در قسمت قبل، سرویس و کامپوننت دریافت اطلاعات اتاق‌ها را از Web API برنامه، تکمیل کردیم. در این قسمت با استفاده از اطلاعات مهیا شده، UI آن‌را نیز تکمیل خواهیم کرد.


نمایش منتظر بمانید در حین بارگذاری اولیه‌ی کامپوننت

کامپوننت‌هایی که قرار است اطلاعات را از یک Web API دریافت کنند، مدتی باید منتظر بمانند تا عملیات رفت و برگشت به سرور، تکمیل شود. در این بین می‌توان یک loading را به کاربر نمایش داد:
@page "/hotel/rooms"

@if (Rooms is not null && Rooms.Any())
{

}
else
{
    <div style="position:fixed;top:50%;left:50%;margin-top:-50px;margin-left:-100px;">
        <img src="images/loader.gif" />
    </div>
}

@code {
    IEnumerable<HotelRoomDTO> Rooms = new List<HotelRoomDTO>();
    // ... 
}
- فیلد Rooms را در قسمت قبل، در متد LoadRooms، از Web API دریافت و مقدار دهی کردیم. تا زمان تکمیل عملیات این متد، فیلد Rooms، فاقد عضوی است؛ بنابراین قسمت else شرط فوق اجرا می‌شود که یک loading را نمایش خواهد داد. مابقی UI برنامه در قسمت if آن قرار می‌گیرد.
- هر زمانیکه کار روال رویدادگردان OnInitializedAsync به پایان برسد (که شامل اجرای متد LoadRooms نیز هست)، سبب فراخوانی خودکار StateHasChanged می‌شود. این فراخوانی، UI را مجددا رندر می‌کند. به همین جهت است که پس از پایان کار، محتوای if، رندر خواهد شد.
- از این loading سفارشی که در میانه‌ی صفحه نمایش داده می‌شود، می‌توان در فایل wwwroot\index.html نیز بجای loading پیش‌فرض آن استفاده کرد:
  <body>
    <div id="app">
      <div
        style="
          position: fixed;
          top: 50%;
          left: 50%;
          margin-top: -50px;
          margin-left: -100px;
        "
      >
        <img src="images/ajax-loader.gif" />
      </div>
    </div>

افزودن خواصی جدید به HotelRoomDTO

می‌خواهیم به کاربر امکان تغییر تعداد روزهای اقامت را بدهیم. این انتخاب باید در لیست اتاق‌های نمایش داده شده، با تغییر تعداد روزهای اقامت (TotalDays) و هزینه‌ی جدید متناظر با آن (TotalAmount)، منعکس شود. به همین جهت این خواص را به HotelRoomDTO، اضافه می‌کنیم:
namespace BlazorServer.Models
{
    public class HotelRoomDTO
    {
        // ...

        public int TotalDays { get; set; }
        public decimal TotalAmount { get; set; }
    }
}
محاسبات مربوط به این خواص را هم می‌توان در همان کامپوننت HotelRooms.razor، پس از بارگذاری لیست اتاق‌ها از Web API، انجام داد:
@code
{
     HomeVM HomeModel = new HomeVM();
    // ...

    private async Task LoadRoomsAsync()
    {
        Rooms = await HotelRoomService.GetHotelRoomsAsync(HomeModel.StartDate, HomeModel.EndDate);
        foreach (var room in Rooms)
        {
            room.TotalAmount = room.RegularRate * HomeModel.NoOfNights;
            room.TotalDays = HomeModel.NoOfNights;
        }
    }
}


افزودن امکان تغییر تعداد روزهای اقامت در همان صفحه‌ی نمایش لیست اتاق‌ها


همانطور که در تصویر فوق هم مشاهده می‌کنید، می‌خواهیم در این صفحه نیز کاربر بتواند زمان شروع اقامت و مدت مدنظر را تغییر دهد. به همین جهت، HomeModel ای را که در قسمت قبل از Local Storage دریافت کردیم، به فرم زیر متصل می‌کنیم تا اجزای آن در این فرم، نمایش داده شده و قابل تغییر شوند:
@if (Rooms is not null && Rooms.Any())
{
    <EditForm Model="HomeModel" OnValidSubmit="SaveBookingInfo" class="bg-light">
        <div class="pt-3 pb-2 px-5 mx-1 mx-md-0 bg-secondary">
            <DataAnnotationsValidator />
            <div class="row px-3 mx-3">
                <div class="col-6 col-md-4">
                    <div class="form-group">
                        <label class="text-warning">Check in Date</label>
                        <InputDate @bind-Value="HomeModel.StartDate" class="form-control" />
                    </div>
                </div>
                <div class="col-6 col-md-4">
                    <div class="form-group">
                        <label class="text-warning">Check Out Date</label>
                        <input @bind="HomeModel.EndDate" disabled="disabled"
                            readonly="readonly" type="date" class="form-control" />
                    </div>
                </div>
                <div class=" col-4 col-md-2">
                    <div class="form-group">
                        <label class="text-warning">No. of nights</label>
                        <select class="form-control" @bind="HomeModel.NoOfNights">
                            <option value="Select" selected disabled="disabled">(Select No. Of Nights)</option>
                            @for (var i = 1; i <= 10; i++)
                            {
                                <option value="@i">@i</option>
                            }
                        </select>
                    </div>
                </div>

                <div class="col-8 col-md-2">
                    <div class="form-group" style="margin-top: 1.9rem !important;">
                        @if (IsProcessing)
                        {
                            <button class="btn btn-success btn-block form-control">
                                <i class="fa fa-spin fa-spinner"></i>Processing...
                            </button>
                        }
                        else
                        {
                            <input type="submit" value="Update" class="btn btn-success btn-block form-control" />
                        }
                    </div>
                </div>
            </div>
        </div>
    </EditForm>
نکته‌ی مهم این فرم، مدیریت قسمت کلیک بر روی دکمه‌ی Update است که سبب فراخوانی روال رویدادگران OnValidSubmit می‌شود:
@code {
    bool IsProcessing;

    // ...

    private async Task SaveBookingInfo()
    {
        IsProcessing = true;
        HomeModel.EndDate = HomeModel.StartDate.AddDays(HomeModel.NoOfNights);
        await LocalStorage.SetItemAsync(ConstantKeys.LocalInitialBooking, HomeModel);
        await LoadRoomsAsync();
        IsProcessing = false;
    }
}
در ابتدای عملیات، فیلد جدید IsProcessing را به true تنظیم می‌کنیم. این مورد سبب می‌شود تا برچسب دکمه‌ی Update به Processing... تغییر کند. سپس فیلد محاسباتی EndDate را بر اساس اطلاعات جدید فرم، به روز رسانی می‌کنیم. در ادامه، مجددا این اطلاعات را در Local Storage ذخیره سازی کرده و کار LoadRoomsAsync را انجام می‌دهیم که به همراه آن، خواص جدید تعداد روزها و هزینه‌ی اقامت نیز مجددا محاسبه می‌شوند. در آخر برچسب دکمه‌ی Update را به حالت اول باز می‌گردانیم.

سؤال: زمانیکه IsProcessing به true تنظیم می‌شود که هنوز کار متد رویدادگردان SaveBookingInfo به پایان نرسیده‌است و فراخوانی خودکار StateHasChanged در پایان متدهای رویدادگردان صورت می‌گیرد. پس چطور است که سبب رندر مجدد UI و تغییر برچسب دکمه‌ی Update می‌شود؟
پاسخ به این سؤال را در قسمت 6 این سری با بررسی چرخه‌ی حیات کامپوننت‌ها، مشاهده کردیم:
«البته متدهای رویدادگردان async، دوبار سبب فراخوانی ضمنی StateHasChanged می‌شوند؛ یکبار زمانیکه قسمت sync متد به پایان می‌رسد (در این مثال یعنی تا قبل از اولین await نوشته شده) و یکبار هم زمانیکه کار فراخوانی کلی متد به پایان خواهد رسید»


نمایش لیست اتاق‌ها


نمایش لیست اتاق‌ها مطابق تصویر فوق، دو قسمت اصلی را دارد:
الف) نمایش لیست تصاویر منتسب به یک اتاق، توسط کامپوننت carousel بوت استرپ
@foreach (var room in Rooms)
{
            <div class="row p-2 my-3 " style="border-radius:20px; border: 1px solid gray">
                <div class="col-12 col-lg-3 col-md-4">
                    <div id="carouselExampleIndicators_@room.Id"
                        class="carousel slide mb-4 m-md-3 m-0 pt-3 pt-md-0"
                        data-ride="carousel">
                        <ol class="carousel-indicators">
                            @{
                                int imageIndex = 0;
                                int innerImageIndex = 0;
                            }
                            @foreach (var image in room.HotelRoomImages)
                            {
                                if (imageIndex == 0)
                                {
                                    <li data-target="#carouselExampleIndicators_@room.Id"
                                        data-slide-to="@imageIndex" class="active"></li>

                                }
                                else
                                {
                                    <li data-target="#carouselExampleIndicators_@room.Id"
                                        data-slide-to="@imageIndex"></li>
                                }
                                imageIndex++;
                            }
                        </ol>
                        <div class="carousel-inner">
                            @foreach (var image in room.HotelRoomImages)
                            {
                                var imageUrl = $"{ImagesBaseAddress}/{image.RoomImageUrl}";
                                if (innerImageIndex == 0)
                                {
                                    <div class="carousel-item active">
                                        <img class="d-block w-100" style="border-radius:20px;"
                                            src="@imageUrl" alt="First slide">
                                    </div>
                                }
                                else
                                {
                                    <div class="carousel-item">
                                        <img class="d-block w-100" style="border-radius:20px;"
                                            src="@imageUrl" alt="First slide">
                                    </div>
                                }

                                innerImageIndex++;
                            }
                        </div>
                        <a class="carousel-control-prev" href="#carouselExampleIndicators_@room.Id"
                            role="button" data-slide="prev">
                            <span class="carousel-control-prev-icon" aria-hidden="true"></span>
                            <span class="sr-only">Previous</span>
                        </a>
                        <a class="carousel-control-next" href="#carouselExampleIndicators_@room.Id"
                            role="button" data-slide="next">
                            <span class="carousel-control-next-icon" aria-hidden="true"></span>
                            <span class="sr-only">Next</span>
                        </a>
                    </div>
                </div>
}
- هرچند این قطعه کد، طولانی به نظر می‌رسد اما قسمت‌های مختلف آن صرفا بر اساس مستندات سایت بوت استرپ، جهت تشکیل ساختار ابتدایی و استاندارد کامپوننت carousel، تهیه شده‌اند.
- سپس در حلقه‌ای که برای نمایش لیست اتاق‌ها تهیه کرده‌ایم، قسمت‌های مختلف carousel را تکمیل می‌کنیم که در اینجا نیاز به ایندکس تصاویر، لیست تصاویر و یک Id منحصربفرد برای این carousel خاص را دارد تا بتوان چندین وهله از آن‌را در صفحه قرار داد که این id را بر اساس Id اتاق مشخص کرد‌ه‌ایم.

دو نکته:
- در این مثال برای تعریف لینک به تصاویر، کد زیر را مشاهده می‌کنید:
var imageUrl = $"{ImagesBaseAddress}/{image.RoomImageUrl}";
و این ImagesBaseAddress، به صورت زیر تعریف شده که همان آدرس برنامه‌ی blazor server ای است که مشخصات اتاق‌ها و تصاویر را ثبت می‌کند:
@code {
   string ImagesBaseAddress = "https://localhost:5006";
بنابراین اگر می‌خواهید تصاویر را هم مشاهده کنید، باید برنامه‌ی مجزای blazor server این سری نیز در حال اجرا باشد.
- کامپوننت carousel برای اجرا، نیاز به فایل lib/bootstrap/dist/js/bootstrap.bundle.min.js را نیز دارد. به همین جهت مدخل اسکریپت آن‌را باید به فایل wwwroot\index.html اضافه کرد.

ب) نمایش جزئیات نام و هزینه‌ی اتاق
قسمت دوم حلقه‌ی foreach نمایش لیست اتاق‌ها، جهت نمایش جزئیات هر اتاق تعریف شده‌است:
@foreach (var room in Rooms)
{
                <div class="col-12 col-lg-9 col-md-8">
                    <div class="row pt-3">
                        <div class="col-12 col-lg-8">
                            <p class="card-title text-warning" style="font-size:xx-large">@room.Name</p>
                            <p class="card-text">
                                @((MarkupString)room.Details)
                            </p>
                        </div>
                        <div class="col-12 col-lg-4">
                            <div class="row pb-3 pt-2">
                                <div class="col-12 col-lg-11 offset-lg-1">
                                    <a href="@($"hotel/room-details/{room.Id}")" class="btn btn-success btn-block">Book</a>
                                </div>
                            </div>
                            <div class="row ">
                                <div class="col-12 pb-5">
                                    <span class="float-right">
                                        <span class="float-right">Occupancy : @room.Occupancy adults </span><br />
                                        <span class="float-right pt-1">Room Size : @room.SqFt sqft</span><br />
                                        <h4 class="text-warning font-weight-bold pt-4">
                                            <span style="border-bottom:1px solid #ff6a00">
                                                @room.TotalAmount.ToString("#,#.00;(#,#.00#)")
                                            </span>
                                        </h4>
                                        <span class="float-right">Cost for  @room.TotalDays nights</span>
                                    </span>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
}
- در این مثال از MarkupString استفاده شده تا بتوان یک محتوای HTML ای را در صفحه نمایش داد.
- هر اتاق نمایش داده شده، لینکی را به صفحه‌ی خاص خودش نیز دارد که آن‌را در قسمت بعدی تکمیل می‌کنیم.
- در اینجا TotalAmount و TotalDays محاسباتی و قابل تغییر بر اساس انتخاب کاربر نیز درج شده‌اند.


یک تمرین: در برنامه‌ی Blazor Server، سرویسی را جهت درج مشخصات امکانات رفاهی هتل تهیه کردیم. این امکانات رفاهی را از طریق Web API برنامه دریافت و سپس در برنامه‌ی سمت کلاینت نمایش دهید.
بنابراین تکمیل این تمرین شامل تهیه‌ی موارد زیر است که کدنویسی آن، با دو قسمت اخیر این سری دقیقا یکی است و نکته‌ی جدیدی را به همراه ندارد (و کدهای کامل آن را از انتهای بحث می‌توانید دریافت کنید):
- تهیه‌ی HotelAmenityController در پروژه‌ی Web API که به کمک IAmenityService، لیست امکانات رفاهی را بازگشت می‌دهد.
- تهیه‌ی ‍ClientHotelAmenityService در پروژه‌ی WASM که همانند ClientHotelRoomService قسمت قبل ، از Web API، لیست HotelAmenityDTO‌ها را دریافت می‌کند.
- ثبت سرویس جدید ‍ClientHotelAmenityService در Program.cs.
- در آخر حلقه‌ای را بر روی لیست HotelAmenityDTO دریافتی از ClientHotelRoomService در کامپوننت Index.razor تشکیل داده و آن‌ها را نمایش می‌دهیم.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-28.zip