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

تعدادی از اجزای مهم سیستم امنیتی این افزونه شامل :
 1 ) ارتباط با وب سرویس سایت سازنده و دریافت کد فعال سازی موقت مختص به سیستم شما و ثبت اطلاعات سیستم شما در بانک اطلاعاتی سایت است برای جلوگیری استفاده مجدد از نسخه‌ی ازمایشی بعد از 21 روز . ( البته بعد از 21 روز فقط تعدای از قابلیت‌ها غیر فعال میشوند و بقیه رایگان هستند از جمله دیباگینگ php در vs !) . 
2 ) ایجاد کد منحصر به فرد هر سیستم .
3 ) ترکیب کد منحصر به فرد با شماره pakage ای که بعد از نصب افزونه vs به ان اختصاص میدهد و ... 

در کل نکات جالبی رو رعایت کرده بود و تعداد زیادی از کلاس‌ها رو پیاده سازی کرده بود که ارتباطاتی با هم داشتند فقط و فقط جهت سخت کردن debuging که از کارش خوشم اومد ...
مطالب
ذخیره TreeView ساخته شده توسط KendoUI در Asp.net MVC
همانطور که از نمونه مثال‌های خود Kendo UI مشاهده میشود ، نحوه استفاده از TreeView آن به صورت زیر است :
<div>
@(Html.Kendo().TreeView()
        .Name("treeview")
        .TemplateId("treeview-template")
        .HtmlAttributes(new { @class = "demo-section" })
        .DragAndDrop(true)
        .BindTo(Model.Where(e=>e.ParentFolderID==null).OrderBy(e=>e.Order), mappings => 
        {
            mappings.For<DAL.Folder>(binding => binding
                    .ItemDataBound((item, folder) =>
                    {
                        item.Text = folder.FolderName;
                        item.SpriteCssClasses = "folder";
                        item.Expanded=true;
                        item.Id = folder.FolderID.ToString();
                    })
                    .Children(folder => folder.Folder1));
            mappings.For<DAL.Folder>(binding => binding
                    .ItemDataBound((item, folder) =>
                    {
                        item.Text = folder.FolderName;
                        item.SpriteCssClasses = " folder";
                        item.Expanded = true;
                        item.Id = folder.FolderID.ToString();
                    }));
        })
)
</div>

<style type="text/css" scoped>
    .demo-section {
        width: 200px;
    }
    
    #treeview  .k-sprite ,#treeview2 .k-sprite {
        background-image: url("@Url.Content("/Content/kendo/images/coloricons-sprite.png")");
    }
    .rootfolder { background-position: 0 0; }
    .folder { background-position: 0 -16px; }
    .pdf { background-position: 0 -32px; }
    .html { background-position: 0 -48px; }
    .image { background-position: 0 -64px; }
    .delete-link,.edit-link {
        width: 12px;
        height: 12px;
        overflow: hidden;
        display: inline-block;
        vertical-align: top;
        margin: 2px 0 0 3px;
        -webkit-border-radius: 5px;
        -mox-border-radius: 5px;
        border-radius: 5px;
    }
    .delete-link{
        background: transparent url("@Url.Content("/Content/kendo/images/close.png")") no-repeat 50% 50%;
    }
    .edit-link{
        background: transparent url("@Url.Content("/Content/kendo/images/edit.png")") no-repeat 50% 50%;
    }
</style>
استفاده از این TreeView ساده است ولی اگر احتیاج داشته باشیم که پس از drag&drop کردن گره‌ها آن را ذخیره کنیم چگونه باید عمل کنیم؟ این ریالسمت در خود Kendo تعبیه نشده است ، پس به صورت زیر عمل میکنیم :
پس از ساختن TreeView و اصلاح آن به شکل دلخواه لینک زیر را در ادامه اش می‌آوریم :
<a href="#" id="serialize">ذخیره</a>
سپس در تگ اسکریپت‌های خود این کد جاوا اسکریپت را برای serialize کردن تمام گره‌های TreeView مینویسیم :
$('#serialize').click(function () {
            serialized = serialize();
            window.location.href = "Folder/SaveMenu?serial=" + serialized + "!";
        });

        function serialize() {
            var tree = $("#treeview").data("kendoTreeView");
            var json = treeToJson(tree.dataSource.view());
            return JSON.stringify(json);
        }

        function treeToJson(nodes) {

            return $.map(nodes, function (n, i) {

                var result = { id: n.id};
                //var result = { text: n.text, id: n.id, expanded: n.expanded, checked: n.checked };
                if (n.hasChildren)
                    result.items = treeToJson(n.children.view());

                return result;
            });
        }
حال به تشریح کدها میپردازیم :
تابع serialize  درخط اول تمام عناصر داخلی treeview را گرفته ، به صورت یک datasource قابل انقیاد درآورده وسپس datasource آن را به یک نمایش قابل تجزیه تبدیل میکند و به متد treeToJSON میفرستد.
var tree = $("#treeview").data("kendoTreeView");
var json = treeToJson(tree.dataSource.view());

تابع  treeToJSON درخت را به عنوان یکسری گره گرفته و تمام عناصر آن را به فرمت json میبرد . (قسمتی که به صورت توضیحی درآمده میتواند برای بدست آوردن تمام اطلاعات گره از جمله متن و انتخاب شدن checkbox آن و غیره مورد استفاده قرار بگیرد) در ادامه این تابع اگر گره درحال استفاده فرزندی داشته باشد به صورت بازگشتی همین تابع برای آن فراخوانده میشود.
var result = { id: n.id};
 //var result = { text: n.text, id: n.id, expanded: n.expanded, checked: n.checked };
 if (n.hasChildren)
 result.items = treeToJson(n.children.view());

سپس اطلاعات برگشتی که در فرمت json هستند در خط آخر serialaize به رشته تبدیل میشوند و به رویداد کلیک که از آن فراخوانده شده بود بازمیگردند. 
return JSON.stringify(json);
خط آخر رویداد نیز یک Action در Controller مورد نظر را هدف قرار میدهد و رشته بدست آمده را به آن ارسال میکند و صفحه redirect میشود.
window.location.href = "Folder/SaveMenu?serial=" + serialized + "!";
رشته ای که با عنوان serialize به کنترلر ارسال میشود مانند زیر است :
"[{\"id\":\"2\"},{\"id\":\"5\",\"items\":[{\"id\":\"3\"},{\"id\":\"6\"},{\"id\":\"7\"}]}]!"
این رشته مربوط به درختی به شکل زیر است :

همانطور که میبینید گره دوم که "پوشه چهارم45" نام دارد شامل سه فرزند است که در رشته داده شده با عنوان item شناخته شده است. حال باید این رشته با برنامه نویسی سی شارپ جداسازی کرد :

string serialized;
Dictionary<int, int> numbers = new Dictionary<int, int>();   
public ActionResult SaveMenu(string serial)
        {
            var newfolders = new List<Folder>();
            serialized = serial;
            calculte_serialized(0);
            return RedirectToAction("Index");
        }
void calculte_serialized(int parent)
        {
            while (serialized.Length > 0)
            {
                var id_index=serialized.IndexOf("id");
                if (id_index == -1)
                {
                    return;
                }
                serialized = serialized.Substring(id_index + 5);
                var quote_index = serialized.IndexOf("\"");
                var id=serialized.Substring(0, quote_index);
                numbers.Add(int.Parse(id), parent);
                serialized = serialized.Substring(quote_index);
                var condition = serialized.Substring(0,3);
                switch (condition)
                {
                    case "\"},":
                        break;
                    case "\",\"":
                        calculte_serialized(int.Parse(id));
                        break;
                    case "\"}]":
                        return;
                        break;
                    default:
                        break;
                }
            }
        }

calculte_serialized با گرفتن 0 کار خود را شروع میکند یعنی از گره هایی که پدر ندارند و تمام id‌ها را همراه با پدرشان در یک دیکشنری میریزد و هرکجا که به فرزندی برخورد به صورت بازگشتی فراخوانی میشود. پس از اجرای کامل آن ما درخت را در یک دیکشنری به صورت عنصرهای مجزا در اختیار داریم که میتوانیم در پایگاه داده ذخیره کنیم.

مطالب
React 16x - قسمت 19 - کار با فرم‌ها - بخش 2 - اعتبارسنجی ورودی‌های کاربران
تمام فرم‌های تعریف شده، نیاز به اعتبارسنجی اطلاعات وارد شده‌ی توسط کاربران خود را دارند. ابتدا اعتبارسنجی اطلاعات را در حین ارسال فرم و سپس آن‌را همزمان با ورود اطلاعات، بررسی می‌کنیم.


اصول کلی طراحی یک اعتبارسنج ساده

در قسمت قبل، تمام اطلاعات فرم لاگین را درون شیء account خاصیت state قرار دادیم. در اینجا نیز شبیه به چنین شیءای را برای ذخیره سازی خطاهای اعتبارسنجی فیلدهای فرم، تعریف می‌کنیم:
class LoginForm extends Component {
  state = {
    account: { username: "", password: "" },
    errors: {
      username: "Username is required"
    }
  };
خاصیت errors تعریف شده، یک شیء را باز می‌گرداند که حاوی اطلاعات و خطاهای مرتبط با اعتبارسنجی فیلدهای مشکل دار است. بنابراین نام خواص این شیء، با نام فیلدهای فرم تطابق دارند. کار کردن با یک شیء هم جهت یافتن خطاهای یک فیلد مشخص، ساده‌تر است از کار کردن با یک آرایه؛ از این جهت که نیازی به جستجوی خاصی در این شیء نبوده و با استفاده از روش دسترسی پویای به خواص یک شیء جاوا اسکریپتی مانند errors["username"]، می‌توان خطاهای مرتبط با هر فیلد را به سادگی نمایش داد.
البته در ابتدای کار، خاصیت errors را با یک شیء خالی ({}) مقدار دهی می‌کنیم و سپس در متد مدیریت ارسال فرم به سرور:
  validate = () => {
    return { username: "Username is required." };
  };

  handleSubmit = e => {
    e.preventDefault();

    const errors = this.validate();
    console.log("Validation errors", errors);
    this.setState({ errors });
    if (errors) {
      return;
    }

    // call the server
    console.log("Submitted!");
  };
- ابتدا خروجی متد validate سفارشی را بررسی می‌کنیم که خروجی آن، خطاهای ممکن است.
- اگر خطایی وجود داشت، به مرحله‌ی بعد که ارسال فرم به سمت سرور می‌باشد، نخواهیم رسید و کار را با یک return، خاتمه می‌دهیم.
- علت فراخوانی متد setState در اینجا، درخواست رندر مجدد فرم، با توجه به خطاهای اعتبارسنجی ممکنی است که به خاصیت errors، اضافه یا به روز رسانی کرده‌ایم.
- نمونه‌ای از خروجی متد validate را نیز در اینجا مشاهده می‌کنید که تشکیل شده‌است از یک شیء، که هر خاصیت آن، به نام یک فیلد موجود در فرم، اشاره می‌کند.


پیاده سازی یک اعتبارسنج ساده

در اینجا یک نمونه پیاده سازی ساده و ابتدایی منطق اعتبارسنجی فیلدهای فرم را ملاحظه می‌کنید:
  validate = () => {
    const { account } = this.state;

    const errors = {};
    if (account.username.trim() === "") {
      errors.username = "Username is required.";
    }

    if (account.password.trim() === "") {
      errors.password = "Password is required.";
    }

    return Object.keys(errors).length === 0 ? null : errors;
  };
- ابتدا توسط Object Destructuring، خاصیت account شیء منتسب به خاصیت state کامپوننت را دریافت می‌کنیم، تا مدام نیاز به نوشتن this.state.account نباشد.
- سپس یک شیء خالی error را تعریف کرده‌ایم.
- در ادامه با توجه به اینکه مقادیر المان‌های فرم در state وجود دارند، خالی بودن آن‌ها را بررسی می‌کنیم. اگر خالی بودند، یک خاصیت جدید را با همان نام المان مورد بررسی، به شیء errors اضافه کرده و پیام خطایی را درج می‌کنیم.
- در نهایت این شیء errors و یا نال را (در صورت عدم وجود خطایی) بازگشت می‌دهیم.

برای آزمایش آن، پس از اجرای برنامه، یکبار بدون وارد کردن اطلاعاتی، بر روی دکمه‌ی Login کلیک کنید؛ یکبار هم با وارد کردن اطلاعاتی در فیلدهای مختلف. در این بین کنسول توسعه دهندگان مرورگر را نیز جهت مشاهده‌ی شیء‌های error لاگ شده، بررسی نمائید.



نمایش خطاهای اعتبارسنجی فیلدهای فرم

در قسمت قبل، کامپوننت جدید src\components\common\input.jsx را جهت کاهش کدهای تکراری تعاریف المان‌های ورودی، ایجاد کردیم. در اینجا نیز می‌توان کار نمایش خطاهای اعتبارسنجی را قرار داد:
import React from "react";

const Input = ({ name, label, value, error, onChange }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input
        value={value}
        onChange={onChange}
        id={name}
        name={name}
        type="text"
        className="form-control"
      />
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Input;
- در اینجا ابتدا خاصیت error را به لیست خواص مورد انتظار از شیء props، اضافه کرده‌ایم.
- سپس با توجه به نکته‌ی «رندر شرطی عناصر در کامپوننت‌های React» در قسمت 5، اگر error مقداری داشته باشد و به true تفسیر شود، آنگاه به صورت خودکار، div ای که دارای کلاس‌های بوت استرپی اخطار است به همراه متن خطا، رندر خواهد شد؛ در غیراینصورت هیچ div ای به صفحه اضافه نمی‌شود.
- اکنون متد رندر کامپوننت فرم لاگین را به صورت زیر تکمیل می‌کنیم:
  render() {
    const { account, errors } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        <Input
          name="username"
          label="Username"
          value={account.username}
          onChange={this.handleChange}
          error={errors.username}
        />
        <Input
          name="password"
          label="Password"
          value={account.password}
          onChange={this.handleChange}
          error={errors.password}
        />
        <button className="btn btn-primary">Login</button>
      </form>
    );
  }
در ابتدای متد رندر، با استفاده از Object Destructuring، خاصیت errors شیء منتسب به خاصیت state کامپوننت را دریافت کرده‌ایم. سپس با استفاده از آن، ویژگی جدید error را که به تعریف کامپوننت Input اضافه کردیم، در دو فیلد username و password، مقدار دهی می‌کنیم.

تا اینجا اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، با کلیک بر روی دکمه‌ی Login، خطاهای اعتبارسنجی به صورت زیر ظاهر می‌شوند:


در این حالت اگر هر دو فیلد را تکمیل کرده و بر روی دکمه‌ی لاگین کلیک کنیم، به خطای زیر در کنسول توسعه دهندگان مرورگر می‌رسیم:
loginForm.jsx:55 Uncaught TypeError: Cannot read property 'username' of null
at LoginForm.render (loginForm.jsx:55)
علت اینجاست که چون فرم اعتبارسنجی شده و مشکلی وجود نداشته‌است، خروجی متد validate در این حالت، null است. بنابراین دیگر نمی‌توان به خاصیت برای مثال username آن دسترسی یافت. برای رفع این مشکل در متد handleSubmit، جائیکه errors را در خاصیت state به روز رسانی می‌کنیم، اگر errors نال باشد، بجای آن یک شیء خالی را بازگشت می‌دهیم:
this.setState({ errors: errors || {} });
این قطعه کد، به این معنا است که اگر errors مقدار دهی شده بود، از آن استفاده کن، در غیراینصورت {} (یک شیء خالی جاوا اسکریپتی) را بازگشت بده.


اعتبارسنجی فیلدهای یک فرم در حین ورود اطلاعات در آن‌ها

تا اینجا نحوه‌ی اعتبارسنجی فیلدهای ورودی را در حین submit بررسی کردیم. شبیه به همین روش را به حالت onChange و متد handleChange فرم لاگین که در قسمت قبل تکمیل کردیم نیز می‌توان اعمال کرد:
  handleChange = ({ currentTarget: input }) => {
    const errors = { ...this.state.errors }; //cloning an object
    const errorMessage = this.validateProperty(input);
    if (errorMessage) {
      errors[input.name] = errorMessage;
    } else {
      delete errors[input.name];
    }

    const account = { ...this.state.account }; //cloning an object
    account[input.name] = input.value;

    this.setState({ account, errors });
  };
- ابتدا شیء errors را clone می‌کنیم؛ چون می‌خواهیم خواصی را به آن کم و زیاد کرده و سپس بر اساس آن مجددا state را به روز رسانی کنیم.
- سپس اینبار فقط نیاز داریم اعتبار اطلاعات ورودی یک فیلد را بررسی کنیم و متد validate فعلی، فیلدهای کل فرم را با هم تعیین اعتبار می‌کند. به همین جهت متد جدید validateProperty را به صورت زیر تعریف می‌کنیم. اگر این متد خروجی داشت، خاصیت متناظر با آن‌را در شیء errors به روز رسانی می‌کنیم؛ در غیراینصورت این خاصیت را از شیء errors حذف می‌کنیم تا پیام اشتباهی را نمایش ندهد. در نهایت توسط متد setState، مقدار خاصیت errors را با شیء errors جاری به روز رسانی می‌کنیم:
  validateProperty = ({ name, value }) => {
    if (name === "username") {
      if (value.trim() === "") {
        return "Username is required.";
      }
      // ...
    }

    if (name === "password") {
      if (value.trim() === "") {
        return "Password is required.";
      }
      // ...
    }
  };
در متد validateProperty، خواص name و value از شیء input ارسالی به آن استخراج شده‌اند و سپس بر اساس آن‌ها کار اعتبارسنجی صورت می‌گیرد.

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


معرفی Joi


تا اینجا، هدف نمایش ساختار یک اعتبارسنج ساده بود. این روش مقیاس پذیر نیست و در ادامه آن‌را با یک کتابخانه‌ی اعتبارسنجی بسیار پیشرفته به نام Joi، جایگزین خواهیم کرد که نمونه مثال‌های آن‌را در اینجا می‌توانید مشاهده کنید:
const Joi = require('@hapi/joi');
const schema = Joi.object({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/),
    email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
});
schema.validate({ username: 'abc', birth_year: 1994 });
ایده‌ی اصلی Joi، تعریف یک اسکیما برای object جاوا اسکریپتی خود است. در این اسکیما، تمام خواص شیء مدنظر ذکر شده و سپس توسط fluent api آن، نیازمندی‌های اعتبارسنجی هرکدام ذکر می‌شوند. برای مثال username باید رشته‌ای بوده، تنها از حروف و اعداد تشکیل شود. حداقل طول آن، 3 و حداکثر طول آن، 30 باشد و همچنین ورود آن نیز اجباری است. با استفاده از pattern آن می‌توان عبارات باقاعده را ذکر کرد و یا با متدهایی مانند email، از قالب خاص مقدار یک خاصیت، اطمینان حاصل کرد.

برای نصب آن، پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
> npm install @hapi/joi --save
> npm i --save-dev @types/hapi__joi
که در نهایت سبب نصب کتابخانه‌ی node_modules\@hapi\joi\dist\joi-browser.min.js خواهند شد و همچنین TypeScript definitions آن‌را نیز نصب می‌کنند که بلافاصله سبب فعالسازی intellisense مخصوص آن در VSCode خواهد شد. بدون نصب types آن، پس از تایپ Joi.، از مزایای تکمیل خودکار fluent api آن توسط VSCode، برخوردار نخواهیم بود.

سپس به کامپوننت فرم لاگین مراجعه کرده و در ابتدای آن، Joi را import می‌کنیم:
import Joi from "@hapi/joi";
پس از آن، اسکیمای شیء account را تعریف خواهیم کرد. اسکیما نیازی نیست جزئی از state باشد؛ چون قرار نیست تغییر کند. به همین جهت آن‌را به صورت یک خاصیت جدید در سطح کلاس کامپوننت تعریف می‌کنیم:
  schema = {
    username: Joi.string()
      .required()
      .label("Username"),
    password: Joi.string()
      .required()
      .label("Password")
  };
خاصیت اسکیما را با یک شیء با ساختار از نوع Joi.object، که خواص آن، با خواص شیء account مرتبط با فیلدهای فرم لاگین، تطابق دارد، تکمیل می‌کنیم. مقدار هر خاصیت نیز با Joi. شروع شده و سپس نوع و محدودیت‌های مدنظر اعتبارسنجی را می‌توان تعریف کرد که در اینجا هر دو مورد باید رشته‌ای بوده و به صورت اجباری وارد شوند. توسط متد label، برچسب نام خاصیت درج شده‌ی در پیام خطای نهایی را می‌توان تنظیم کرد. اگر از این متد استفاده نشود، از همان نام خاصیت ذکر شده استفاده می‌کند.
سپس ابتدای متد validate قبلی را به صورت زیر بازنویسی می‌کنیم:
  validate = () => {
    const { account } = this.state;
    const validationResult = Joi.object(this.schema).validate(account, {
      abortEarly: false
    });
    console.log("validationResult", validationResult);
ابتدا مقدار خاصیت account، از شیء state استخراج شده‌است که حاوی شیءای با اطلاعات نام کاربری و کلمه‌ی عبور است. سپس این شیء را به متد validate خاصیت اسکیمایی که تعریف کردیم، ارسال می‌کنیم. Joi، اعتبارسنجی را به محض یافتن خطایی، متوقف می‌کند. به همین جهت تنظیم abortEarly آن به false صورت گرفته‌است تا تمام خطاهای اعتبارسنجی را نمایش دهد.
اکنون اگر برنامه را اجرا کرده و بدون ورود اطلاعاتی، بر روی دکمه‌ی لاگین کلیک کنیم، خروجی زیر در کنسول توسعه دهندگان مرورگر ظاهر می‌شود:


همانطور که مشاهده می‌کنید، خروجی Joi، یک شیء است که اگر دارای خاصیت error بود، یعنی خطای اعتبارسنجی رخ‌داده‌است. سپس باید خاصیت آرایه‌ای details این شیء error را جهت یافتن خواص مشکل دار بررسی کرد. هر خاصیت در اینجا با path مشخص می‌شود. بنابراین قدم بعدی، تبدیل این ساختار، به ساختار شیء errors موجود در state کامپوننت جاری است تا مابقی برنامه بتواند بدون تغییری از آن استفاده کند.


نگاشت شیء دریافتی از Joi، به شیء errors موجود در state کامپوننت لاگین

خاصیت error شیء دریافتی از متد validate کتابخانه‌ی Joi، تنها زمانی ظاهر می‌شود که خطایی وجود داشته باشد. همچنین خاصیت details آن نیز آرایه‌ا‌ی از اشیاء با خواص message و path است. این path نیز یک آرایه است که اولین المان آن، نام خاصیت در حال بررسی است. اکنون می‌خواهیم این آرایه را تبدیل به یک شیء قابل درک برای برنامه کنیم:
  validate = () => {
    const { account } = this.state;
    const validationResult = Joi.object(this.schema).validate(account, {
      abortEarly: false
    });
    console.log("validationResult", validationResult);

    if (!validationResult.error) {
      return null;
    }

    const errors = {};
    for (let item of validationResult.error.details) {
      errors[item.path[0]] = item.message;
    }
    return errors;
  };
ابتدا بررسی می‌کنیم که آیا خاصیت error، تنظیم شده‌است یا خیر؟ اگر خیر، کار این متد به پایان می‌رسد. سپس حلقه‌ای را بر روی آرایه‌ی details، تشکیل می‌دهیم تا شیء errors مدنظر ما را با خاصیت دریافتی از path و پیام دریافتی متناظری تکمیل کند. در آخر این شیء errors با ساختار مدنظر خود را بازگشت می‌دهیم.

اکنون اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، همانند قبل می‌توان خطاهای اعتبارسنجی در حین submit را مشاهده کرد:



بازنویسی متد validateProperty توسط Joi

تا اینجا متد validate ساده و ابتدایی خود را با استفاده از امکانات کتابخانه‌ی Joi، بازنویسی کردیم. اکنون نوبت بازنویسی متد اعتبارسنجی در حین تایپ اطلاعات است:
  validateProperty = ({ name, value }) => {
    const userInputObject = { [name]: value };
    const propertySchema = Joi.object({ [name]: this.schema[name] });
    const { error } = propertySchema.validate(userInputObject, {
      abortEarly: true
    });
    return error ? error.details[0].message : null;
  };
تفاوت این متد با متد validate، در اعتبارسنجی تنها یک خاصیت از شیء account موجود در state است؛ به همین جهت نمی‌توان کل this.state.account را به متد validate کتابخانه‌ی Joi ارسال کرد. بنابراین نیاز است بر اساس name و value رسیده‌ی از کاربر، یک شیء جدید را به صورت پویا تولید کرد. در اینجا روش تعریف { [name]: value } به computed properties معرفی شده‌ی در ES6 اشاره می‌کند. اگر در حین تعریف یک شیء، برای مثال بنویسیم {"username:"value}، این username به صورت "username" ثابتی تفسیر می‌شود و پویا نیست. اما در ES6 می‌توان با استفاده از [] ها، تعریف نام یک خاصیت را پویا کرد که نمونه‌ای از آن‌را در userInputObject و همچنین propertySchema مشاهده می‌کنید.
تا اینجا بجای this.state.account که به کل فرم اشاره می‌کند، شیء اختصاصی‌تر userInputObject را ایجاد کرده‌ایم که معادل اطلاعات فیلد ورودی کاربر است. یک چنین نکته‌ای را در مورد schema نیز باید رعایت کرد. در اینجا نمی‌توان اعتبارسنجی را با this.schema شروع کرد؛ چون این شیء نیز به اطلاعات کل فرم اشاره می‌کند و نه تک فیلدی که هم اکنون در حال کار با آن هستیم. بنابراین نیاز است یک Joi.object جدید را بر اساس name رسیده، از this.schema کلی، استخراج و تولید کرد؛ مانند propertySchema که مشاهده می‌کنید.
اکنون می‌توان متد validate این شیء اسکیمای جدید را فراخوانی کرد و خاصیت error شیء حاصل از آن‌را توسط Object Destructuring، استخراج نمود. در اینجا abortEarly به true تنظیم شده‌است (البته حالت پیش‌فرض آن است و نیازی به ذکر صریح آن نیست). علت اینجا است که نمایش خطاهای بیش از اندازه به کاربر، برای او گیج کننده خواهند بود؛ به همین جهت هربار، اولین خطای حاصل را به او نمایش می‌دهیم.


غیرفعالسازی دکمه‌ی submit در صورت شکست اعتبارسنجی‌های فرم

در ادامه می‌خواهیم دکمه‌ی submit فرم لاگین، تا زمانیکه اعتبارسنجی آن با موفقیت به پایان برسد، غیرفعال باشد:
 <button disabled={this.validate()} className="btn btn-primary">
 Login
</button>
ویژگی disabled را اگر به true تنظیم کنیم، این دکمه را غیرفعال می‌کند. در اینجا متد validate تعریف شده، نال و یا یک شیء را بازگشت می‌دهد. اگر نال را بازگشت دهد، در جاوااسکریپت به false تفسیر شده و دکمه را فعال می‌کند و برعکس؛ مانند تصویر حاصل زیر:


فراخوانی setState‌های تعریف شده‌ی در متدهای رویدادگردان این فرم هستند که سبب رندر مجدد کامپوننت شده و در این بین در متد رندر، کار بررسی مجدد مقدار نهایی متد validate صورت می‌گیرد تا بر اساس آن، فعال یا غیرفعال بودن دکمه‌ی Login، مشخص شود.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-19.zip
اشتراک‌ها
بهترین تمرینها برای ASP.NET Core Web API

In this post, we are going to write about what we consider to be the best practices while developing the .NET Core Web API project. How we can make it better and how to make it more maintainable.

We are going to go through the following sections:

بهترین تمرینها برای ASP.NET Core Web API
اشتراک‌ها
مخزن آموزشی Net. Core با مثالهای پیاده سازی شده

در این مخزن روش‌های بهینه و توصیه شده جهت ساخت برنامه‌های تحت وب با استفاده از Net Core. در قالب 12 پروژه پیاده سازی شده است که منبع خوبی جهت الگو برداری است.

Boilerplate for ASP.NET Core reference application with Entity Framework Core, demonstrating a layered application architecture with DDD best practices. Implements NLayer Hexagonal architecture (Core, Application, Infrastructure and Presentation Layers) and Domain Driven Design (Entities, Repositories, Domain/Application Services, DTO's...) and aimed to be a Clean Architecture, with applying SOLID principles in order to use for a project template. Also implements best practices like loosely-coupled, dependency-inverted architecture and using design patterns such as Dependency Injection, logging, validation, exception handling, localization and so on. 

مخزن آموزشی Net. Core با مثالهای پیاده سازی شده
اشتراک‌ها
Angular 5.2 منتشر شد
- Improved type checking for templates
- Support for TypeScript 2.6
- Improved Router Param & Data Inheritance
Angular 5.2 منتشر شد
مطالب
بررسی تغییرات Blazor 8x - قسمت هشتم - مدیریت انتقال اطلاعات Pre-Rendering سمت سرور، به جزایر تعاملی
این Prerendering است که امکان رندر یک کامپوننت تعاملی را در سمت سرور میسر می‌کند تا کاربر بتواند پیش از فعال شدن قابلیت‌های پیشرفته‌ی یک کامپوننت، یک حداقل خروجی را از آن مشاهده کند و همچنین وجود آن برای موتورهای جستجو و بهبود SEO بسیار مفید است. اما ... در این بین مشکلی رخ می‌دهد که نمونه‌ی آن‌را در قسمت قبل مشاهده کردیم: آغاز آن دوبار صورت می‌گیرد؛ یکبار در سمت سرور برای تهیه‌ی یک خروجی SSR و یکبار هم پس از فعال شدن قابلیت‌های تعاملی آن در سمت کلاینت. این آغاز دوباره، برای هر دو حالت کامپوننت‌های تعاملی Blazor Server و Blazor WASM برقرار است. راه‌حل‌هایی از نحوه‌ی مواجه شدن با یک چنین مشکلی را در قسمت قبل بررسی کردیم. راه‌حل دیگری که در این بین ارائه شده و توسط خود مایکروسافت هم در مثال‌های آن مورد استفاده قرار می‌گیرد، استفاده از سرویس PersistentComponentState است که جزئیات آن‌را در این قسمت بررسی خواهیم کرد.


بررسی نحوه‌ی عملکرد سرویس PersistentComponentState

سرویس PersistentComponentState، در دات‌نت 6، به Blazor اضافه شد و امکان جدیدی نیست. قسمتی از این مباحث جدید SSR که به‌نظر مختص به Blazor 8x هستند، پیشتر هم وجود داشتند؛ تحت عنوان pre-rendering. برای مثال فقط کافی بودن تا در برنامه‌های Blazor Server قبلی، فایل Host.cshtml_ را به صورت زیر ویرایش کرد تا pre-rendering فعال شود:
<component type="typeof(App)" render-mode="ServerPrerendered" />
مشکلی که در این حالت بروز می‌کرد این بود که متد OnInitializedAsync یک کامپوننت، دوبار فراخوانی می‌شد؛ یکبار در زمان pre-rendering در سمت سرور، تا HTML استاتیکی برای ارائه‌ی به مرورگر کاربر تولید شود و بار دیگر در زمان فعال شدن اتصال SignalR کامپوننت و ارائه‌ی نهایی تعاملی آن. به همین جهت، کار سنگین آغازین یک کامپوننت، دوبار انجام می‌شد که تجربه‌ی کاربری ناخوشایندی را هم به همراه داشت. برای رفع این مشکل، tag helper ویژه‌ای را به نام persist-component-state تهیه کردند که به صورت زیر به فایل host.cshtml_ اضافه می‌شد:
<body>
    <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> 
    <persist-component-state />
</body>
این tag-helper فوق است که کار درج رمزنگاری شده‌ی اطلاعات کش شده‌ی pre-rendering سمت سرور را در انتهای فایل HTML صفحه انجام می‌دهد و برای نمونه، نحوه‌ی استفاده‌ی از آن به صورت زیر است:
@page "/"

@implements IDisposable
@inject PersistentComponentState applicationState
@inject IUserRepository userRepository

@foreach(var user in users)
{
    <ShowUserInformation user="@item"></ShowUserInformation>
}

@code {
    private const string cachingKey = "something_unique";
    private List<User> users = new();
    private PersistingComponentStateSubscription persistingSubscription;
    
    protected override async Task OnInitializedAsync()
    {
        persistingSubscription = applicationState.RegisterOnPersisting(PersistData);

        if (applicationState.TryTakeFromJson<List<User>>(cachingKey, out var restored))
        {
            users = restored;
        }
        else 
        {
            users = await userRepository.GetAllUsers();
        }
    }

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

    private Task PersistData()
    {
        applicationState.PersistAsJson(cachingKey, users);
        return Task.CompletedTask;
    }

}
توضیحات:
- ابتدا تزریق سرویس PersistentComponentState را مشاهده می‌کنید. این همان کامپوننتی است که کار کش کردن اطلاعات را مدیریت می‌کند.
- سپس فراخوانی متد RegisterOnPersisting انجام شده‌است. متدی که توسط آن ثبت می‌شود، در حین عملیات pre-rendering فراخوانی می‌شود تا اطلاعاتی را کش کند. نحوه‌ی این کش شدن را در ادامه‌ی مطلب بررسی می‌کنیم.
- سپس فراخوانی متد TryTakeFromJson رخ‌داده‌است. اگر این متد اطلاعاتی را برگرداند، یعنی pre-rendering سمت سرور پیشتر انجام شده و اطلاعاتی کش شده، برای دریافت در سمت کلاینت، وجود داشته و نیازی به مراجعه‌ی مجدد و دوباره، به بانک اطلاعاتی نیست.
- دراینجا یک قسمت Dispose را هم مشاهده می‌کنید تا اگر کاربر به صفحه‌ی دیگری مراجعه کرد، متد ثبت شده‌ی در اینجا، از لیست مواردی که باید در حین pre-rendering سریالایز شوند، حذف گردد.

اگر از این روش استفاده نشود، به علت دوبار فراخوانی شدن متد OnInitializedAsync یک کامپوننت به همراه pre-rendering، اطلاعاتی که در حین pre-rendering کامپوننت از بانک اطلاعاتی، برای تولید محتوای استاتیک صفحه در سمت سرور دریافت شده، با فعالسازی آتی تعاملی سمت کلاینت آن کامپوننت، از دست خواهد رفت و در این حالت کلاینت باید مجددا این اطلاعات را از بانک اطلاعاتی درخواست کند. بنابراین هدف از persist-component-state، حفظ داده‌ها در بین دو رندر است؛ رندر اولی که در سمت سرور انجام می‌شود و رندر دومی که متعاقبا در سمت کلاینت رخ می‌دهد.

یک نکته: به یک چنین قابلیتی در فریم‌ورک‌های دیگر «hydration» هم گفته می‌شود که در اصل یک شیء دیکشنری است که مقدار شیء حالت را در سمت سرور دریافت کرده و آن‌را در زمان فعال شدن سمت کلاینت کامپوننت، برای استفاده‌ی مجدد مهیا می‌کند.


سؤال: اطلاعات سرویس PersistentComponentState کجا ذخیره می‌شوند؟

همانطور که در مثال فوق هم مشاهده کردید، سرویس PersistentComponentState، این state را به صورت JSON ذخیره می‌کند. اما ... این اطلاعات دقیقا کجا ذخیره می‌شوند؟
برای مشاهده‌ی آن‌ها، فقط کافی است به source صفحه مراجعه کنید تا با دو مقدار مخفی رمزنگاری شده‌ی زیر در انتهای HTML صفحه مواجه شوید!
<!--Blazor-Server-Component-State:CfDJXyz->
<!--Blazor-WebAssembly-Component-State:eyJVc2Xyz->
فرمت این اطلاعات، JsonSerializer.SerializeToUtf8Bytes رمزنگاری شده‌ی توسط IDataProtectionProvider است. این هم یک روش نگهداری اطلاعات، بجای استفاده از کش مرورگر است؛ بدون نیاز به استفاده از جاوااسکریپت و کار با local storage و امثال آن.

مایکروسافت از این نوع کارها پیشتر در ASP.NET Web forms توسط ViewStateها انجام داده‌است! البته ViewStateها جهت نگهداری اطلاعات وضعیت کلاینت، به سمت سرور ارسال می‌شوند؛ اما این Component-Stateهای مخفی، فقط یکبار توسط قسمت کلاینت خوانده می‌شوند و هدف آن‌ها ارسال اطلاعاتی به سمت سرور نیست.

یک نکته: همانطور که عنوان شد، در نگارش‌های قبلی Blazor، از تگ‌هلپری به نام persist-component-state برای درج این اطلاعات در انتهای صفحه استفاده می‌کردند. استفاده از این تگ‌هلپر در Blazor 8x منسوخ شده و به صورت خودکار داده‌های تمام سرویس‌های PersistentComponentState فعالی که توسط PersistAsJson اطلاعاتی را ذخیره می‌کنند، جمع آوری و به صورت یکجا در انتهای صفحه به صورت رمزنگاری شده، درج می‌کنند.


روش خاموش کردن Pre-rendering

برای خاموش کردن pre-rendering نیاز است پارامتر هم‌نامی را به نحو زیر با false مقدار دهی کرد:
@rendermode renderMode

@code 
{
    private static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false);
}


بازنویسی مثال قسمت قبل با استفاده از سرویس PersistentComponentState

در قسمت قبل هرچند روش مواجه شدن با مشکل دوبار فراخوانی شدن متد آغازین یک کامپوننت را در سمت سرور و کلاینت بررسی کردیم، اما ... در نهایت دوبار مراجعه به بانک اطلاعاتی انجام می‌شود؛ یکبار در زمان pre-rendering و با مراجعه‌ی مستقیم به یک سرویس سمت سرور و یکبار دیگر هم در زمان فراخوانی httpClient.GetFromJsonAsync در سمت کلاینت برای دریافت اطلاعات مورد نیاز از یک Web API Endpoint. اگر بخواهیم اطلاعات لیست محصولات دریافتی سمت سرور را به سمت کلاینت منتقل کنیم و مجددا آن‌را از بانک اطلاعاتی دریافت نکنیم، راه‌حل سوم آن، استفاده از سرویس PersistentComponentState است.

در کدهای زیر، بازنویسی کامپوننت محصولات مشابه را مشاهده می‌کنید که کمی پیچیده‌تر شده‌است. اینبار لیست محصولات مشابه، در همان بار اول رندر کامپوننت نمایش داده می‌شوند و نه پس از کلیک بر روی دکمه‌ای. به همین جهت باید کار مدیریت دوبار فراخوانی متد رویدادگردان OnInitializedAsync را به درستی انجام داد. متد OnInitializedAsync یکبار در حین پیش‌رندر سمت سرور اجرا می‌شود و بار دیگر زمانیکه وب‌اسمبلی جاری در مرورگر به صورت کامل دریافت شده و فعال می‌شود؛ یعنی برای بار دوم، کدهای اجرایی آن سمت کلاینت هستند.
در این مثال با استفاده از سرویس PersistentComponentState، اطلاعات دریافت شده‌ی در حین pre-rendering ابتدایی رخ‌داده‌ی در سمت سرور، در متد OnPersistingAsync، کش شده و در حین فعال شدن وب‌اسمبلی مرتبط در سمت کلاینت، با استفاده از متد PersistentState.TryTakeFromJson، از کش خوانده می‌شوند و دیگر دوبار رفت و برگشت به بانک اطلاعاتی را شاهد نخواهیم بود.

@implements IDisposable

@inject IProductStore ProductStore
@inject PersistentComponentState PersistentState

<h3>Related products</h3>

@if (_relatedProducts == null)
{
    <p>Loading...</p>
}
else
{
    <div class="mt-3">
        @foreach (var item in _relatedProducts)
        {
            <a href="/ProductDetails/@item.Id">
                <div class="col-sm">
                    <h5 class="mt-0">@item.Title (@item.Price)</h5>
                </div>
            </a>
        }
    </div>
}

@code{

    private const string StateCachingKey = "products";
    private IList<Product>? _relatedProducts;
    private PersistingComponentStateSubscription _persistingSubscription;

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

    protected override async Task OnInitializedAsync()
    {
        _persistingSubscription = PersistentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);

        if (PersistentState.TryTakeFromJson<IList<Product>>(StateCachingKey, out var restored))
        {
            _relatedProducts = restored;
        }
        else
        {
            await Task.Delay(500); // Simulates asynchronous loading
            _relatedProducts = await ProductStore.GetRelatedProducts(ProductId);
        }
    }

    private Task OnPersistingAsync()
    {
        PersistentState.PersistAsJson(StateCachingKey, _relatedProducts);
        return Task.CompletedTask;
    }

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

}
در این پیاده سازی هنوز هم از سرویس IProductStore استفاده می‌شود که دو نگارش سمت سرور و سمت کلاینت آن‌را در قسمت قبل تهیه کردیم. برای مثال باتوجه به اینکه کدهای فوق در حین pre-rendering در سمت سرور اجرا می‌شوند، به صورت خودکار از نگارش سمت سرور IProductStore استفاده خواهد شد.

نکته‌ی مهم: فعلا کدهای فوق فقط برای حالت بارگذاری اولیه‌ی کامپوننت درست کار می‌کنند. یعنی اگر نگارش وب‌اسمبلی کامپوننت پس از وقوع پیش‌رندر سمت سرور، در مرورگر دریافت و بارگذاری کامل شود؛ اما برای دفعات بعدی مراجعه‌ی به این صفحه با استفاده از enhanced navigation و راهبری بهبود یافته که در قسمت ششم این سری بررسی کردیم ... دیگر کار نمی‌کنند و در این حالت کش شدنی رخ نمی‌دهد که در نتیجه، شاهد دوبار رفت و برگشت به بانک اطلاعاتی خواهیم بود و اساسا استفاده‌ی از PersistentComponentState را زیر سؤال می‌برد؛ چون در حین راهبری بهبود یافته، از آن استفاده نمی‌شود (قسمت PersistentState.TryTakeFromJson آن، هیچگاه اطلاعاتی را از کش نمی‌خواند). اطلاعات بیشتر