مطالب
فعال سازی قسمت آپلود تصویر و فایل Kendo UI Editor
یکی دیگر از ویجت‌های Kendo UI یک HTML Editor کامل است به همراه امکانات ارسال فایل، تصویر و ... پشتیبانی از راست به چپ. در ادامه قصد داریم نحوه‌ی مدیریت نمایش لیست فایل‌ها، افزودن و حذف آن‌ها را از طریق این ادیتور بررسی کنیم.


تنظیمات ابتدایی Kendo UI Editor

در ذیل کدهای سمت کاربر فعال سازی مقدماتی Kendo UI را مشاهده می‌کنید. در قسمت tools آن، لیست امکانات و نوار ابزار مهیای آن درج شده‌اند.
دو مورد insertImage و insertFile آن نیاز به تنظیمات سمت کاربر و سرور بیشتری دارند.
<!--نحوه‌ی راست به چپ سازی -->
<div class="k-rtl">
    <textarea id="editor" rows="10" cols="30" style="height: 440px"></textarea>
</div>
 
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#editor").kendoEditor({
                tools: [
                    "bold", "italic", "underline", "strikethrough", "justifyLeft",
                    "justifyCenter", "justifyRight", "justifyFull", "insertUnorderedList",
                    "insertOrderedList", "indent", "outdent", "createLink", "unlink",
                    "insertImage", "insertFile",
                    "subscript", "superscript", "createTable", "addRowAbove", "addRowBelow",
                    "addColumnLeft", "addColumnRight", "deleteRow", "deleteColumn", "viewHtml",
                    "formatting", "cleanFormatting", "fontName", "fontSize", "foreColor",
                    "backColor", "print"
                ],
                imageBrowser: {
                    messages: {
                        dropFilesHere: "فایل‌های خود را به اینجا کشیده و رها کنید"
                    },
                    transport: {
                        read: {
                            url: "@Url.Action("GetFilesList", "KendoEditorImages")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET',
                            cache: false
                        },
                        destroy: {
                            url: "@Url.Action("DestroyFile", "KendoEditorImages")",
                            type: "POST"
                        },
                        create: {
                            url: "@Url.Action("CreateFolder", "KendoEditorImages")",
                            type: "POST"
                        },
                        thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")",
                        uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")",
                        imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}"
                    }
                },
                fileBrowser: {
                    messages: {
                        dropFilesHere: "فایل‌های خود را به اینجا کشیده و رها کنید"
                    },
                    transport: {
                        read: {
                            url: "@Url.Action("GetFilesList", "KendoEditorFiles")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET',
                            cache: false
                        },
                        destroy: {
                            url: "@Url.Action("DestroyFile", "KendoEditorFiles")",
                            type: "POST"
                        },
                        create: {
                            url: "@Url.Action("CreateFolder", "KendoEditorFiles")",
                            type: "POST"
                        },
                        uploadUrl: "@Url.Action("UploadFile", "KendoEditorFiles")",
                        fileUrl: "@Url.Action("GetFile", "KendoEditorFiles")?path={0}"
                    }
                }
            });
        });
    </script>
}
در اینجا نحوه‌ی تنظیم مسیرهای مختلف ارسال فایل و تصویر Kendo UI Editor را ملاحظه می‌کنید.
منهای قسمت thumbnailUrl، عملکرد قسمت‌های مختلف افزودن فایل و تصویر این ادیتور یکسان هستند. به همین جهت می‌توان برای مثال کنترلی مانند KendoEditorFilesController را ایجاد و سپس در کنترلر KendoEditorImagesController از آن ارث بری کرد و متد دریافت و نمایش بند انگشتی تصاویر را افزود. به این ترتیب دیگر نیازی به تکرار کدهای مشترک بین این دو قسمت نخواهد بود.


نمایش لیست پوشه‌ها و تصویر در ابتدای باز شدن صفحه‌ی درج تصویر

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


تنظیمات خواندن این فایل‌ها، از قسمت read مربوط به imageBrowser دریافت می‌شود که cache آن نیز به false تنظیم شده‌است تا در این بین مرورگر اطلاعات را کش نکند. این مورد در حین حذف فایل‌ها و پوشه‌ها مهم است. زیرا اگر cache:false تنظیم نشده باشد، حذف یک فایل یا پوشه در سمت کاربر تاثیری نخواهد داشت.
imageBrowser: {
    transport: {
        read: {
            url: "@Url.Action("GetFilesList", "KendoEditorImages")",
            dataType: "json",
            contentType: 'application/json; charset=utf-8',
            type: 'GET',
            cache: false
        }
    }
},
در ادامه نیاز است اکشن متد GetFilesList را به نحو ذیل در سمت سرور تهیه کرد:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpGet]
        public ActionResult GetFilesList(string path)
        {
            path = GetSafeDirPath(path);
            var imagesList = new DirectoryInfo(path)
                                .GetFiles()
                                .Select(fileInfo => new KendoFile
                                {
                                    Name = fileInfo.Name,
                                    Size = fileInfo.Length,
                                    Type = KendoFileType
                                }).ToList();
 
            var foldersList = new DirectoryInfo(path)
                                .GetDirectories()
                                .Select(directoryInfo => new KendoFile
                                {
                                    Name = directoryInfo.Name,
                                    Type = KendoDirType
                                }).ToList();
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(imagesList.Union(foldersList), new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
 
 
        protected string GetSafeDirPath(string path)
        {
            // path = مسیر زیر پوشه‌ی وارد شده
            if (string.IsNullOrWhiteSpace(path))
            {
                return Server.MapPath(FilesFolder);
            }
 
            //تمیز سازی امنیتی
            path = Path.GetDirectoryName(path);
            path = Path.Combine(Server.MapPath(FilesFolder), path);
            return path;
        } 
    }
}
در اینجا کدهای کلاس پایه KendoEditorFilesController را مشاهده می‌کنید. به این جهت فیلد FilesFolder آن protected تعریف شده‌است تا در کلاسی که از آن ارث بری می‌کند نیز قابل دسترسی باشد. سپس لیست فایل‌ها و پوشه‌های path دریافتی با فرمت لیستی از KendoFile تهیه شده و با فرمت JSON بازگشت داده می‌شوند. ساختار KendoFile را در ذیل مشاهده می‌کنید:
namespace KendoUI13.Models
{
    public class KendoFile
    {
        public string Name { set; get; }
        public string Type { set; get; }
        public long Size { set; get; }
    }
}
- در اینجا Type می‌تواند از نوع فایل با مقدار f و یا از نوع پوشه با مقدار d باشد.
- علت استفاده از CamelCasePropertyNamesContractResolver در حین بازگشت JSON نهایی، تبدیل خواص دات نتی، به نام‌های سازگار با JavaScript است. برای مثال به صورت خودکار Name را تبدیل به name می‌کند.
- پارامتر path در ابتدای کار خالی است. اما کاربر می‌تواند در بین پوشه‌های باز شده‌ی توسط مرورگر تصاویر Kendo UI حرکت کند. به همین جهت مقدار آن باید هربار بررسی شده و بر این اساس لیست فایل‌ها و پوشه‌های جاری بازگشت داده شوند.


مدیریت حذف تصاویر و پوشه‌ها

همانطور که در شکل فوق نیز مشخص است، با انتخاب یک پوشه یا فایل، دکمه‌ای با آیکن ضربدر جهت فراهم آوردن امکان حذف، ظاهر می‌شود. این دکمه متصل است به قسمت destroy تنظیمات ادیتور:
imageBrowser: {
    transport: {
        destroy: {
            url: "@Url.Action("DestroyFile", "KendoEditorImages")",
            type: "POST"
        }
    }
},
این تنظیمات سمت کاربر را باید به نحو ذیل در سمت سرور مدیریت کرد:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpPost]
        public ActionResult DestroyFile(string name, string path)
        {
            //تمیز سازی امنیتی
            name = Path.GetFileName(name);
            path = GetSafeDirPath(path);
 
            var pathToDelete = Path.Combine(path, name);
 
            var attr = System.IO.File.GetAttributes(pathToDelete);
            if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
            {
                Directory.Delete(pathToDelete, recursive: true);
            }
            else
            {
                System.IO.File.Delete(pathToDelete);
            }
 
            return Json(new object[0]);
        } 
    }
}
- استفاده از Path.GetFileName جهت دریافت نام فایل‌ها در اینجا بسیار مهم است. زیرا اگر این تمیز سازی امنیتی صورت نگیرد، ممکن است با کمی تغییر در آن، فایل web.config برنامه، دریافت یا حذف شود.
- پارامتر name دریافتی مساوی است با نام فایل انتخاب شده و path مشخص می‌کند که در کدام پوشه قرار داریم.
- چون در اینجا امکان حذف یک پوشه یا فایل وجود دارد، حتما نیاز است بررسی کنیم، مسیر دریافتی پوشه‌است یا فایل و سپس بر این اساس جهت حذف آن‌ها اقدام صورت گیرد.


مدیریت ایجاد یک پوشه‌ی جدید

تنظیمات قسمت create مرورگر تصاویر، مرتبط است به زمانیکه کاربر با کلیک بر روی دکمه‌ی +، درخواست ایجاد یک پوشه‌ی جدید را کرده‌است:
imageBrowser: {
    transport: {
        create: {
            url: "@Url.Action("CreateFolder", "KendoEditorImages")",
            type: "POST"
        }
    }
},
کدهای اکشن متد متناظر با این عمل را در ذیل مشاهده می‌کنید:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpPost]
        public ActionResult CreateFolder(string name, string path)
        {
            //تمیز سازی امنیتی
            name = Path.GetFileName(name);
            path = GetSafeDirPath(path);
            var dirToCreate = Path.Combine(path, name);
 
            Directory.CreateDirectory(dirToCreate);
 
            return KendoFile(new KendoFile
            {
                Name = name,
                Type = KendoDirType
            });
        }
 
        protected ActionResult KendoFile(KendoFile file)
        {
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(file,
                    new JsonSerializerSettings
                    {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
- در اینجا نیز name مساوی نام پوشه‌ی درخواستی است و path به مسیر تو در توی پوشه‌ی جاری اشاره می‌کند.
- پس از ایجاد پوشه، باید نام آن‌را با فرمت KendoFile به صورت JSON بازگشت داد. همچنین در اینجا Type را نیز باید به d (پوشه) تنظیم کرد.


مدیریت قسمت ارسال فایل و تصویر

زمانیکه کاربر بر روی دکمه‌ی upload file یا بارگذاری تصاویر در اینجا کلیک می‌کند، اطلاعات فایل آپلودی به مسیر uploadUrl ارسال می‌گردد.
imageBrowser: {
    transport: {
        thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")",
        uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")",
        imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}"
    }
},
دو تنظیم دیگر thumbnailUrl و imageUrl، برای نمایش بند انگشتی و نمایش کامل تصویر کاربرد دارند.
در ادامه کدهای مدیریت سمت سرور قسمت آپلود این ادیتور را مشاهده می‌کنید:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";

 
        [HttpPost]
        public ActionResult UploadFile(HttpPostedFileBase file, string path)
        {
            //تمیز سازی امنیتی
            var name = Path.GetFileName(file.FileName);
            path = GetSafeDirPath(path);
            var pathToSave = Path.Combine(path, name);
 
            file.SaveAs(pathToSave);
 
            return KendoFile(new KendoFile
            {
                Name = name,
                Size = file.ContentLength,
                Type = KendoFileType
            });
        } 
    }
}
- در اینجا path مشخص می‌کند که در کدام پوشه‌ی تو در تو قرار داریم و file نیز حاوی محتوای ارسالی به سرور است.
- پس از ذخیره سازی اطلاعات فایل، نیاز است اطلاعات فایل نهایی را با فرمت KendoFile به صورت JSON بازگشت دهیم.


ارث بری از KendoEditorFilesController جهت تکمیل قسمت مدیریت تصاویر

تا اینجا کدهایی را که ملاحظه کردید، برای هر دو قسمت ارسال تصویر و فایل کاربرد دارند. قسمت ارسال تصاویر برای تکمیل نیاز به متد دریافت تصاویر به صورت بند انگشتی نیز دارد که به صورت ذیل قابل تعریف است و چون از کلاس پایه KendoEditorFilesController ارث بری کرده‌است، این کنترلر به صورت خودکار حاوی اکشن متدهای کلاس پایه نیز خواهد بود.
using System.Web.Mvc;
 
namespace KendoUI13.Controllers
{
    public class KendoEditorImagesController : KendoEditorFilesController
    {
        public KendoEditorImagesController()
        {
            // بازنویسی مسیر پوشه‌ی فایل‌ها
            FilesFolder = "~/images";
        }
 
        [HttpGet]
        [OutputCache(Duration = 3600, VaryByParam = "path")]
        public ActionResult GetThumbnail(string path)
        {
            //todo: create thumb/ resize image
 
            path = GetSafeFileAndDirPath(path);
            return File(path, "image/png");
        }
    }
}

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات اشتراک‌ها
بررسی آینده‌ی Blazor در دات نت 8
خلاصه‌ی این مفهوم «United» به این صورت است:
- blazor server از لحاظ عدم نیاز به دریافت چند صد فایل مرتبط با web assembly و همراه بودن با رندر سمت سرور، در ابتدای کار نمایش برنامه، سریعتر نمایش داده می‌شود و این نمایش اولیه تقریبا آنی است.
- Blazor WASM چون به طور کامل در مرورگر اجرا می‌شود و در جهت رندر صفحات نیازی به رفت و برگشت به سرور ندارد، سرعت اجرایی فوق العاده‌ای دارد؛ اما به همراه بارگذاری تعداد زیادی فایل در ابتدای کار نمایش آن است که ... کمی طول می‌کشد که البته می‌توان با استفاده از فعال سازی per-rendering کمی آن‌را سریعتر کرد که نیاز به تنظیمات قابل توجهی دارد که شاید همه از آن استفاده نکنند.
اکنون قرار است در دات نت 8 بتوان به صورت خودکار ابتدا یک صفحه را با استفاده از server side rendering موجود در Blazor Server، سریع نمایش داد (بدون نیاز به دریافت فایل‌های WASM و منتظر شدن برای آن‌ها) و بعد در همان حال در پشت صحنه و به صورت خودکار، همین برنامه با قالب Blazor web assembly هم دریافت می‌شود تا در بار بعدی که این صفحه قرار است نمایش داده شود، بتوان به حداکثر سرعت کار با Blazor، با استفاده از اجرای کامل آن در مرورگر توسط web assembly رسید. یعنی ترکیب همزمان blazor server و blazor web assembly، بدون نیاز به تنظیمات خاصی در جهت از پیش رندر کردن صفحات و یا منتظر ماندن اولیه و نمایش یک صفحه‌ی خالی در جهت بارگذاری تمام فایل‌های یک برنامه‌ی web assembly؛ هر دو با هم به صورت یکپارچه و یک‌دست. حتی می‌توان مشخص کرد که صفحه‌ای فقط بر اساس blazor server اجرا شود و صفحه‌ای دیگر فقط قسمتی از آن از blazor wasm استفاده کند.
مطالب
رفع اشکال خطای transport layer در Analysis Services

رفع اشکال خطای an error was encountered in the transport layer در هنگام وصل شدن از کلاینتی که در یک کامپیوتر دیگر نصب شده است به Sql Server Analysis Services


در هنگام برخورد با این مشکل، پس از بررسی‌ها و تست‌های مختلف و پیاده کردن روشهای متفاوتی که در وب مطرح شده بود، به فکرم رسید که شاید از طریق درج مستقیم پورت بتوان مشکل را حل کرد که مراحل آن به شرح ذیل می‌باشد:

برای بدست آوردن پورتی ( Port ) که Analysis Services به آن گوش می‌دهد و با آن کار می‌کند، باید کارهای ذیل انجام شوند:

از طریق Sql Server Configuration Manager، همانطور که در تصویر آمده است، PID یا Process Id را  که مربوط به Sql Server Analysis Services  می‌شود، برای هر نمونه‌ای ( Instance ) که می‌خواهیم به آن وصل شویم، بدست می‌آوریم:

سپس از طریق Command Prompt دستور ذیل را اجرا می‌کنیم:

 netstat /abo >>c:\output.txt
PID ایی را که در مرحله‌ی قبل بدست آوردیم، مانند تصویر ذیل در فایل output.txt جستجو می‌کنیم و پورت Analysis Services را بدست می‌آوریم:

پس از آن کافی است که در رشته‌ی اتصال به Analysis Services از آن Port استفاده کنیم:

و یا

مطالب
نمایش ای‌جکسی یک partial view در popover بوت استرپ 3
فرض کنید بخواهیم نمایش رای دهنده‌های یک مطلب را با popover بوت استرپ 3 نمایش دهیم:

Popover بوت استرپ برای کار با منابع remote طراحی نشده‌است و نیاز است توابع API آن‌را به همراه jQuery Ajax ترکیب کرد تا به تصویر فوق رسید.


مرحله‌ی اول: اکشن متدی که یک partial view را باز می‌گرداند

فرض کنید اکشن متدی که لیست کاربران رای داده‌ی به یک مطلب را باز می‌گرداند، چنین شکلی را دارد:
        public ActionResult RenderResults(string param1)
        {
            var users = new[]
            {
                new User{ Id = 1, Name = "Test 1", Rating = 3},
                new User{ Id = 2, Name = "Test 2", Rating = 4},
                new User{ Id = 3, Name = "Test 3", Rating = 5}
            };
            return PartialView("_RenderResults", model: users);
        }
در اینجا لیستی دریافت و سپس به partial view ارسال می‌شود. در ادامه این Partial view نیز به صورت ذیل اطلاعات را رندر می‌کند:
@using RemotePopOver.Models
@model IList<User>

<ul id="ratings1" data-title="Ratings" class="list-unstyled">
    @foreach (var user in Model)
    {
        <li>
            @user.Name
            <span class="badge pull-right">@user.Rating</span>
        </li>
    }
</ul>
در اینجا از یک ویژگی سفارشی data-* به نام data-title نیز استفاده کرده‌ایم. متنی که در آن قرار می‌گیرد، به صورت عنوان popover ظاهر خواهد شد.


مرحله‌ی دوم: دریافت اطلاعات partial view با استفاده از jQuery Ajax و سپس درج آن در یک popover

می‌خواهیم با حرکت ماوس بر روی دکمه‌ی سفارشی ذیل، یک popover ظاهر شده و محتوای خودش را از اکشن متد فوق تامین کند.
<span id="remotePopover1"
      aria-hidden="true"
      data-param1="test"
      data-popover-content-url="@Url.Action("RenderResults", "Home")"
      class="glyphicon glyphicon-info-sign btn btn-info"></span>
در این مثال، چند ویژگی سفارشی data-* دیگر را نیز تعریف کرده‌ایم تا نیازی به تعریف سراسری متغیرهای جاوا اسکریپتی نباشد. برای مثال data-param1 یک پارامتر دلخواه است و data-popover-content-url به آدرس اکشن متدی اشاره می‌کند که قرار است partial view مدنظر را رندر کند.
در ادامه نحوه‌ی استفاده‌ی از این ویژگی‌ها را در jQuery Ajax مشاهده می‌کنید:
@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('body').on('mouseenter', 'span[data-popover-content-url]', function () {
                var el = $(this);
                $.ajax({
                    type: "POST",
                    url: $(this).data("popover-content-url"),
                    data: JSON.stringify({ param1: $(this).data("param1") }),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    // controller is returning a simple text, not json
                    complete: function (xhr, status) {
                        var data = xhr.responseText;
                        if (status === 'error' || !data) {
                            el.popover({
                                content: 'Error connecting server!',
                                trigger: 'focus',
                                html: true,
                                container: 'body',
                                placement: 'auto',
                                title: 'Error!'
                            }).popover('show');
                        }
                        else {
                            el.popover({
                                content: data,
                                trigger: 'focus',
                                html: true,
                                container: 'body',
                                placement: 'auto',
                                title: $('<html />').html(data).find('#ratings1:first').data('title')
                            }).popover('show');
                        }
                    }
                });
            }).on('mouseleave', 'span[data-popover-content-url]', function () {
                $(this).popover('hide');
            });
        });
    </script>
}
در این مثال تمام المان‌هایی که دارای data-popover-content-url هستند، تحت نظر قرار می‌گیرند. سپس اگر ماوس به محدوده‌ی آن‌ها وارد شد، مقدار ("this).data("popover-content-url)$ مساوی آدرسی است که قرار است اطلاعات را از سرور دریافت کند. به همین جهت از آن برای استفاده‌ی در متد ajax کمک گرفته شده‌است.
خروجی partial view به صورت json نیست. بنابراین باید اطلاعات نهایی آن‌را در callback ویژه‌ی complete دریافت کرد. مقدار data دریافتی، معادل اطلاعات رندر شده‌ی partial view است. به همین جهت آن‌را به خاصیت content متد popver ارسال می‌کنیم. همچنین چون خروجی patrtial view به همراه html است، نیاز است خاصیت html متد popover نیز به true تنظیم شود. در خاصیت title، نحوه‌ی دسترسی به مقدار data-title تنظیم شده‌ی در partial view را مشاهده می‌کنید.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
RemotePopOver.zip
نظرات مطالب
Vue.js - بحث Event Handling - آشنایی و شیوۀ استفاده از رویدادها - قسمت پنجم
یک نکته‌ی تکمیلی: نحوه‌ی انتخاب یک المنت در Vue.js

در Vue.js  چندین روش برای انتخاب یک المنت وجود دارند که در ادامه آن‌ها را مورد بررسی قرار میدهیم.
 کد زیر را در نظر بگیرید، ما قصد داریم value موجود در input  را نمایش دهیم.

روش اول: با استفاده از جاوا اسکریپت
<!DOCTYPE html>

<head>
    <meta charset="UTF-8">
    <title>Vue.js گرفتن یک المنت در </title>
</head>

<body>
    <div id="main">
        <input type="text" name="fullName" id="fullName">
        <button @click='getFullNameValue()'>Show Me</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        new Vue({
            el: '#main',
            data() {
                return {

                }
            },
            methods: {
                getFullNameValue() {
                    let fullNameValue = document.querySelector('#fullName').value;
                    alert(fullNameValue);
                }
            }
        });
    </script>
</body>

</html>
با استفاده از دستور زیر در جاوا اسکریپت براحتی میتوانیم یک المنت را انتخاب کنیم و آن را به یک متغیر انتساب دهیم.
 let fullNameValue = document.querySelector('#fullName')

روش دوم: انتخاب یک المنت با جی کوئری (با سلکتور دلخواه)
getFullNameValue() {
                    //let fullNameValue = document.querySelector('#fullName').value;
                    let fullNameValue = $('#fullName');
                    alert(fullNameValue.val());
}

روش سوم: انتخاب یک المنت در حیطه نمونه سازی شده  Vue.js
getFullNameValue() {
                    //let fullNameValue = document.querySelector('#fullName').value;
                    //let fullNameValue = $('#fullName').val();
                    let fullNameValue = this.$el.querySelector('#fullName');
                    alert(fullNameValue.value );
}

روش چهارم: انتخاب یک المنت از طریق امکان ref  در  Vue.js
<!DOCTYPE html>

<head>
    <meta charset="UTF-8">
    <title>Vue.js گرفتن یک المنت در </title>
</head>

<body>
    <div id="main">
        <input type="text" name="fullName" id="fullName" ref="refFullName">
        <button @click='getFullNameValue()'>Show Me</button>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        new Vue({
            el: '#main',
            data() {
                return {

                }
            },
            methods: {
                getFullNameValue() {
                    //let fullNameValue = document.querySelector('#fullName').value;
                    //let fullNameValue = $('#fullName').val();
                    //let fullNameValue = this.$el.querySelector('#fullName').value;
                    let fullNameValue = this.$refs.refFullName;
                    alert(fullNameValue.value);
                }
            }
        });
    </script>
</body>

</html>

نتیجه گیری:
از لحاظ performance  روش سوم و چهارم بهینه می‌باشند؛ زیرا فقط در حیطه نمونه سازی شده  Vue.js  مربوطه جستجوی المنت را انجام میدهند. درحالیکه در روش اول و دوم کل DOM  مورد بررسی قرار میگیرد. روش چهارم بخاطر سهولت نوشتاری بهتر از سایر موارد می‌باشد.
چند نکته در مورد کد فوق:
A) در ES6 نیازی به نوشتن کلمه کلیدی function برای تعریف یک متد نیست.
 getFullNameValue: function() {
                  ...
                }
و میتوان بصورتی که در مثال کد استفاده شده، آن را خلاصه نمود.
B)  در ورژن جدید  Vue.js  بجای استفاده از دایرکتیو  v-on  ( کد زیر ) برای تعریف یک رویداد میتوان از  @ استفاه نمود. در مثال مورد استفاده‌ی مقاله از @ استفاده شده است.
  <button v-on:click='getFullNameValue()'>Show Me</button>

مطالب
آشنایی با NHibernate - قسمت دهم

آشنایی با کتابخانه NHibernate Validator

پروژه جدیدی به پروژه NHibernate Contrib در سایت سورس فورج اضافه شده است به نام NHibernate Validator که از آدرس زیر قابل دریافت است:


این پروژه که توسط Dario Quintana توسعه یافته است، امکان اعتبار سنجی اطلاعات را پیش از افزوده شدن آن‌ها به دیتابیس به دو صورت دستی و یا خودکار و یکپارچه با NHibernate فراهم می‌سازد؛ که امروز قصد بررسی آن‌را داریم.

کامپایل پروژه اعتبار سنجی NHibernate

پس از دریافت آخرین نگارش موجود کتابخانه NHibernate Validator از سایت سورس فورج، فایل پروژه آن‌را در VS.Net گشوده و یکبار آن‌را کامپایل نمائید تا فایل اسمبلی NHibernate.Validator.dll حاصل گردد.

بررسی مدل برنامه

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


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

namespace NHSample5.Domain
{
public class Patient
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
}

using System.Collections.Generic;

namespace NHSample5.Domain
{
public class Doctor
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Patient> Patients { get; set; }

public Doctor()
{
Patients = new List<Patient>();
}
}
}
برنامه این قسمت از نوع کنسول با ارجاعاتی به اسمبلی‌های FluentNHibernate.dll ،log4net.dll ،NHibernate.dll ، NHibernate.ByteCode.Castle.dll ،NHibernate.Linq.dll ،NHibernate.Validator.dll و System.Data.Services.dll است.

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


اطلاعات این برنامه بر مبنای NHRepository و NHSessionManager ایی است که در قسمت‌های قبل توسعه دادیم و پیشنیاز ضروری مطالعه آن می‌باشند (سورس پیوست شده شامل نمونه تکمیل شده این موارد نیز هست). همچنین از قسمت ایجاد دیتابیس از روی مدل نیز صرفنظر می‌شود و همانند قسمت‌های قبل است.


تعریف اعتبار سنجی دومین با کمک ویژگی‌ها (attributes)

فرض کنید می‌خواهیم بر روی طول نام و نام خانوادگی بیمار محدودیت قرار داده و آن‌ها را با کمک کتابخانه NHibernate Validator ، اعتبار سنجی کنیم. برای این منظور ابتدا فضای نام NHibernate.Validator.Constraints به کلاس بیمار اضافه شده و سپس با کمک ویژگی‌هایی که در این کتابخانه تعریف شده‌اند می‌توان قیود خود را به خواص کلاس تعریف شده اعمال نمود که نمونه‌ای از آن را مشاهده می‌نمائید:

using NHibernate.Validator.Constraints;

namespace NHSample5.Domain
{
public class Patient
{
public virtual int Id { get; set; }

[Length(Min = 3, Max = 20,Message="طول نام باید بین 3 و 20 کاراکتر باشد")]
public virtual string FirstName { get; set; }

[Length(Min = 3, Max = 60, Message = "طول نام خانوادگی باید بین 3 و 60 کاراکتر باشد")]
public virtual string LastName { get; set; }
}
}
اعمال این قیود از این جهت مهم هستند که نباید وقت برنامه و سیستم را با دریافت خطای نهایی از دیتابیس تلف کرد. آیا بهتر نیست قبل از اینکه اطلاعات به دیتابیس وارد شوند و رفت و برگشتی در شبکه صورت گیرد، مشخص گردد که این فیلد حتما نباید خالی باشد یا طول آن باید دارای شرایط خاصی باشد و امثال آن؟

مثالی دیگر:
جهت اجباری کردن و همچنین اعمال Regular expressions برای اعتبار سنجی یک فیلد می‌توان دو ویژگی زیر را به بالای آن فیلد مورد نظر افزود:

[NotNull]
[Pattern(Regex = "[A-Za-z0-9]+")]

تعریف اعتبار سنجی با کمک کلاس ValidationDef

راه دوم تعریف اعتبار سنجی، کمک گرفتن از کلاس ValidationDef این کتابخانه و استفاده از روش fluent configuration است. برای این منظور، پوشه جدیدی را به برنامه به نام Validation اضافه خواهیم کرد و سپس دو کلاس DoctorDef و PatientDef را به آن به صورت زیر خواهیم افزود:

using NHibernate.Validator.Cfg.Loquacious;
using NHSample5.Domain;

namespace NHSample5.Validation
{
public class DoctorDef : ValidationDef<Doctor>
{
public DoctorDef()
{
Define(x => x.Name).LengthBetween(3, 50);
Define(x => x.Patients).NotNullableAndNotEmpty();
}
}
}

using NHSample5.Domain;
using NHibernate.Validator.Cfg.Loquacious;

namespace NHSample5.Validation
{
public class PatientDef : ValidationDef<Patient>
{
public PatientDef()
{
Define(x => x.FirstName)
.LengthBetween(3, 20)
.WithMessage("طول نام باید بین 3 و 20 کاراکتر باشد");

Define(x => x.LastName)
.LengthBetween(3, 60)
.WithMessage("طول نام خانوادگی باید بین 3 و 60 کاراکتر باشد");
}
}
}

استفاده از قیودات تعریف شده به صورت دستی

می‌توان از این کتابخانه اعتبار سنجی به صورت مستقیم نیز اضافه کرد. روش انجام آن‌را در متد زیر مشاهده می‌نمائید.

/// <summary>
/// استفاده از اعتبار سنجی ویژه به صورت مستقیم
/// در صورت استفاده از ویژگی‌ها
/// </summary>
static void WithoutConfiguringTheEngine()
{
//تعریف یک بیمار غیر معتبر
var patient1 = new Patient() { FirstName = "V", LastName = "N" };
var ve = new ValidatorEngine();
var invalidValues = ve.Validate(patient1);
if (invalidValues.Length == 0)
{
Console.WriteLine("patient1 is valid.");
}
else
{
Console.WriteLine("patient1 is NOT valid!");
//نمایش پیغام‌های تعریف شده مربوط به هر فیلد
foreach (var invalidValue in invalidValues)
{
Console.WriteLine(
"{0}: {1}",
invalidValue.PropertyName,
invalidValue.Message);
}
}

//تعریف یک بیمار معتبر بر اساس قیودات اعمالی
var patient2 = new Patient() { FirstName = "وحید", LastName = "نصیری" };
if (ve.IsValid(patient2))
{
Console.WriteLine("patient2 is valid.");
}
else
{
Console.WriteLine("patient2 is NOT valid!");
}
}
ابتدا شیء ValidatorEngine تعریف شده و سپس متد Validate آن بر روی شیء بیماری غیر معتبر فراخوانی می‌گردد. در صورتیکه این عتبار سنجی با موفقیت روبر نشود، خروجی این متد آرایه‌ای خواهد بود از فیلدهای غیرمعتبر به همراه پیغام‌هایی که برای آن‌ها تعریف کرده‌ایم. یا می‌توان به سادگی همانند بیمار شماره دو، تنها از متد IsValid آن نیز استفاده کرد.

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

public static ValidatorEngine GetFluentlyConfiguredEngine()
{
var vtor = new ValidatorEngine();
var configuration = new FluentConfiguration();
configuration
.Register(
Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => t.Namespace.Equals("NHSample5.Validation"))
.ValidationDefinitions()
)
.SetDefaultValidatorMode(ValidatorMode.UseExternal);
vtor.Configure(configuration);
return vtor;
}

FluentConfiguration آن مجزا است از نمونه مشابه کتابخانه Fluent NHibernate و نباید با آن اشتباه گرفته شود (در فضای نام NHibernate.Validator.Cfg.Loquacious تعریف شده است).
در این متد کلاس‌های قرار گرفته در پوشه Validation برنامه که دارای فضای نام NHSample5.Validation هستند، به عنوان کلاس‌هایی که باید اطلاعات لازم مربوط به اعتبار سنجی را از آنان دریافت کرد معرفی شده‌اند.
همچنین ValidatorMode نیز به صورت External تعریف شده و منظور از External در اینجا هر چیزی بجز استفاده از روش بکارگیری attributes است (علاوه بر امکان تعریف این قیودات در یک پروژه class library مجزا و مشخص ساختن اسمبلی آن در اینجا).

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

/// <summary>
/// استفاده از اعتبار سنجی ویژه به صورت مستقیم
/// در صورت تعریف آن‌ها با کمک
/// ValidationDef
/// </summary>
static void WithConfiguringTheEngine()
{
var ve2 = VeConfig.GetFluentlyConfiguredEngine();
var doctor1 = new Doctor() { Name = "S" };
if (ve2.IsValid(doctor1))
{
Console.WriteLine("doctor1 is valid.");
}
else
{
Console.WriteLine("doctor1 is NOT valid!");
}

var patient1 = new Patient() { FirstName = "وحید", LastName = "نصیری" };
if (ve2.IsValid(patient1))
{
Console.WriteLine("patient1 is valid.");
}
else
{
Console.WriteLine("patient1 is NOT valid!");
}

var doctor2 = new Doctor() { Name = "شمس", Patients = new List<Patient>() { patient1 } };
if (ve2.IsValid(doctor2))
{
Console.WriteLine("doctor2 is valid.");
}
else
{
Console.WriteLine("doctor2 is NOT valid!");
}
}

نکته مهم:
فراخوانی GetFluentlyConfiguredEngine نیز باید یکبار در طول برنامه صورت گرفته و سپس حاصل آن بارها مورد استفاده قرار گیرد. بنابراین نحوه‌ی صحیح دسترسی به آن باید حتما از طریق الگوی Singleton که در قسمت‌های قبل در مورد آن بحث شد، انجام شود.


استفاده از قیودات تعریف شده و سیستم اعتبار سنجی به صورت یکپارچه با NHibernate

کتابخانه NHibernate Validator زمانیکه با NHibernate یکپارچه گردد دو رخداد PreInsert و PreUpdate آن‌را به صورت خودکار تحت نظر قرار داده و پیش از اینکه اطلاعات ثبت و یا به روز شوند، ابتدا کار اعتبار سنجی خود را انجام داده و اگر اعتبار سنجی مورد نظر با شکست مواجه شود، با ایجاد یک exception از ادامه برنامه جلوگیری می‌کند. در این حالت استثنای حاصل شده از نوع InvalidStateException خواهد بود.

برای انجام این مرحله یکپارچه سازی ابتدا متد BuildIntegratedFluentlyConfiguredEngine را به شکل زیر باید فراخوانی نمائیم:

/// <summary>
/// از این کانفیگ برای آغاز سشن فکتوری باید کمک گرفته شود
/// </summary>
/// <param name="nhConfiguration"></param>
public static void BuildIntegratedFluentlyConfiguredEngine(ref Configuration nhConfiguration)
{
var vtor = new ValidatorEngine();
var configuration = new FluentConfiguration();
configuration
.Register(
Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => t.Namespace.Equals("NHSample5.Validation"))
.ValidationDefinitions()
)
.SetDefaultValidatorMode(ValidatorMode.UseExternal)
.IntegrateWithNHibernate
.ApplyingDDLConstraints()
.And
.RegisteringListeners();
vtor.Configure(configuration);

//Registering of Listeners and DDL-applying here
ValidatorInitializer.Initialize(nhConfiguration, vtor);
}
این متد کار دریافت Configuration مرتبط با NHibernate را جهت اعمال تنظیمات اعتبار سنجی به آن انجام می‌دهد. سپس از nhConfiguration تغییر یافته در این متد جهت ایجاد سشن فکتوری استفاده خواهیم کرد (در غیر اینصورت سشن فکتوری درکی از اعتبار سنجی‌های تعریف شده نخواهد داشت). اگر قسمت‌های قبل را مطالعه کرده باشید، کلاس SingletonCore را جهت مدیریت بهینه‌ی سشن فکتوری به خاطر دارید. این کلاس اکنون باید به شکل زیر وصله شود:

SingletonCore()
{
Configuration cfg = DbConfig.GetConfig().BuildConfiguration();
VeConfig.BuildIntegratedFluentlyConfiguredEngine(ref cfg);
//با همان کانفیگ تنظیم شده برای اعتبار سنجی باید کار شروع شود
_sessionFactory = cfg.BuildSessionFactory();
}

از این لحظه به بعد، نیاز به فراخوانی متدهای Validate و یا IsValid نبوده و کار اعتبار سنجی به صورت خودکار و یکپارچه با NHibernate انجام می‌شود. لطفا به مثال زیر دقت بفرمائید:

/// <summary>
/// استفاده از اعتبار سنجی یکپارچه و خودکار
/// </summary>
static void tryToSaveInvalidPatient()
{
using (Repository<Patient> repo = new Repository<Patient>())
{
try
{
var patient1 = new Patient() { FirstName = "V", LastName = "N" };
repo.Save(patient1);
}
catch (InvalidStateException ex)
{
Console.WriteLine("Validation failed!");
foreach (var invalidValue in ex.GetInvalidValues())
Console.WriteLine(
"{0}: {1}",
invalidValue.PropertyName,
invalidValue.Message);
log4net.LogManager.GetLogger("NHibernate.SQL").Error(ex);
}
}
}

/// <summary>
/// استفاده از اعتبار سنجی یکپارچه و خودکار
/// </summary>
static void tryToSaveValidPatient()
{
using (Repository<Patient> repo = new Repository<Patient>())
{
var patient1 = new Patient() { FirstName = "Vahid", LastName = "Nasiri" };
repo.Save(patient1);
}
}
در اینجا از کلاس Repository که در قسمت‌های قبل توسعه دادیم، استفاده شده است. در متد tryToSaveInvalidPatient ، بدلیل استفاده از تعریف بیماری غیرمعتبر، پیش از انجام عملیات ثبت، استثنایی حاصل شده و پیش از هرگونه رفت و برگشتی به دیتابیس، سیستم از بروز این مشکل مطلع خواهد شد. همچنین پیغام‌هایی را که هنگام تعریف قیودات مشخص کرده بودیم را نیز توسط آرایه ex.GetInvalidValues می‌توان دریافت کرد.

نکته:
اگر کار ساخت database schema را با کمک کانفیگ تنظیم شده توسط کتابخانه اعتبار سنجی آغاز کنیم، طول فیلدها دقیقا مطابق با حداکثر طول مشخص شده در قسمت تعاریف قیود هر یک از فیلدها تشکیل می‌گردد (حاصل از اعمال متد ApplyingDDLConstraints در متد BuildIntegratedFluentlyConfiguredEngine ذکر شده می‌باشد).

public static void CreateValidDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند

Configuration cfg = DbConfig.GetConfig().BuildConfiguration();
VeConfig.BuildIntegratedFluentlyConfiguredEngine(ref cfg);
//با همان کانفیگ تنظیم شده برای اعتبار سنجی باید کار شروع شود

new SchemaExport(cfg).Execute(script, export, dropTables);
}


دریافت سورس کامل قسمت دهم


مطالب
بررسی تغییرات Blazor 8x - قسمت نهم - معرفی حالت رندر تعاملی خودکار
Auto Render Mode، آخرین حالت رندری است که به Blazor 8x اضافه شده‌است. اگر از Blazor Server استفاده کنیم، به یک آغاز سریع در برنامه خواهیم رسید، به همراه مقداری تاخیر جزئی، برای به روز رسانی UI؛ از این جهت که تعاملات صورت گرفته باید از طریق اتصال وب‌سوکت SignalR به سرور ارسال شده و منتظر نتیجه‌ی نهایی، برای اعمال آن به صفحه شد و یا باید به مقیاس پذیری این اتصالات همزمان با تعداد کاربران بالا هم اندیشید. اگر از Blazor WASM استفاده کنیم، آغاز آن، اندکی کند خواهد بود تا فایل‌های فریم‌ورک و برنامه، به درون مرورگر کاربر منتقل شوند. اما پس از آن همه‌چیز بسیار سریع است؛ از این جهت که تعاملات با DOM، توسط مرورگر و در همان سمت کاربر مدیریت می‌شود.
اما ... چقدر خوب می‌شد که امکان ترکیب هردوی این‌ها با هم در یک برنامه وجود می‌داشت؛ یعنی داشتن یک آغاز سریع، به همراه تعاملات سریع با DOM. به همین جهت Auto Render Mode به Blazor 8x اضافه شده‌است.


نحوه‌ی عملکرد حالت رندر تعاملی خودکار در Blazor 8x

زمانیکه از قرار است از Auto Render Mode استفاده شود، یعنی در نهایت به سراغ حالت رندر وب‌اسمبلی رفتن؛ اما به شرطی‌که که فریم‌ورک، مطمئن شود می‌تواند تمام فایل‌های مرتبط را خیلی سریع و در کمتر از 100 میلی‌ثانیه تامین کند که عموما یک چنین حالتی به معنای از پیش دریافت کردن این فایل‌ها و کش شده بودن آن‌ها در مرورگر است. اما اگر یک چنین تضمینی وجود نداشته باشد، از همان ابتدای کار تصمیم می‌گیرد که باید کامپوننت را از طریق نگارش Blazor Server آن ارائه دهد، تا آغاز سریعی را سبب شود. در این بین هم در پشت صحنه (یعنی زمانیکه کاربر مشغول به کار با نگارش Blazor Server کامپوننت است)، شروع به دریافت فایل‌های مرتبط با نگارش وب‌اسمبلی کامپوننت و برنامه می‌شود تا آن‌ها را کش کرده و برای بار بعدی بارگذاری صفحه و نمایش اطلاعات آن، به سرعت از آن‌ها استفاده کند.
یک چنین حالتی برای کاربران به این معنا است که به محض گشودن برنامه و صفحه‌ای، قادر به استفاده‌ی از آن هستند و برای بارهای بعدی استفاده، دیگر نیازی به اتصال دائم SignalR یک جزیره‌ی تعاملی Blazor Server نداشته و در نتیجه بار کمتری به سرور تحمیل خواهد شد (مقیاس پذیری بیشتر) و همچنین پردازش DOM بسیار سریعتری را نیز شاهد خواهند بود (کار با نگارش Blazor WASM درون مرورگر).


همانطور که در این تصویر هم مشخص است، برای بار اول نمایش یک چنین جزیره‌هایی، یک اتصال وب‌سوکت برقرار می‌شود که به معنای فعال شدن حالت جزیره‌ای Blazor Server است که در قسمت پنجم بررسی کردیم. در این بین فایل‌های Blazor WASM این جزیره هم دریافت و کش می‌شوند که در کنسول توسعه دهنده‌های مرورگر، لاگ شده‌است. این اتصال وب‌سوکت، در بار اول نمایش این کامپوننت، بسته نخواهد شد؛ تا زمانیکه کاربر به صفحه‌ای دیگر مراجعه کند. در دفعه‌ی بعدی که درخواست نمایش این صفحه را داشته باشیم، چون اطلاعات نگارش وب‌اسمبلی آن کش شده‌است، از همان ابتدای کار نگارش وب اسمبلی را بارگذاری و راه‌اندازی می‌کند.


تفاوت قالب پروژه‌های Auto Render Mode با سایر حالت‌های رندر در Blazor 8x

برای ایجاد قالب ابتدایی پروژه‌ی یک چنین حالت رندری، از دستور dotnet new blazor --interactivity Auto استفاده می‌شود که حالت تعاملی آن به Auto تنظیم شده‌است. در نگاه اول، Solution ایجاد شده‌ی آن، بسیار شبیه به Solution جزیره‌های تعاملی Blazor WASM است که در قسمت هفتم به همراه یک مثال کامل بررسی کردیم؛ یعنی از دو پروژه‌ی سمت سرور و سمت کلاینت تشکیل می‌شود و دارای این تفاوت‌ها است:
در فایل Program.cs پروژه‌ی سمت سرور آن، افزوده شدن هر دو حالت جزایر تعاملی Blazor Server و همچنین Blazor WASM را مشاهده می‌کنیم:
// ...

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

// ...

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(Counter).Assembly);
یک چنین قالبی می‌تواند تمام موارد زیر را با هم در یک Solution پشتیبانی کند:
الف) امکان تعریف صفحات فقط SSR در پروژه‌ی سمت سرور
ب) امکان داشتن جزیره‌های تعاملی فقط Blazor Server در پروژه‌ی سمت سرور
ج) امکان داشتن جزیره‌های تعاملی فقط Blazor Wasm در پروژه‌ی سمت کلاینت
د) به همراه امکان تعریف جزیرهای تعاملی Auto Render Mode در پروژه‌ی سمت کلاینت


یک نکته: در این تنظیمات، متد AddAdditionalAssemblies، امکان استفاده از کامپوننت‌های قرار گرفته‌ی در سایر اسمبلی‌ها و پروژه‌ها را میسر می‌کند.


نحوه‌ی تعریف کامپوننت‌هایی که قرار است توسط Auto Render Mode ارائه شوند

باتوجه به اینکه این نوع کامپوننت‌ها در نهایت قرار است به صورت وب‌اسمبلی رندر شوند، آن‌ها را باید در پروژه‌ی سمت کلاینت قرار داد و به نکات مرتبط با توسعه‌ی آن‌ها که در قسمت هفتم پرداختیم، توجه داشت.
همچنین مانند سایر حالت‌های رندر، به دو طریق می‌توان مشخص کرد که یک کامپوننت باید به چه صورتی رندر شود:
الف) استفاده از دایرکتیو حالت رندر با مقدار InteractiveAuto در ابتدای تعریف یک کامپوننت
@rendermode InteractiveAuto
ب) مشخص کردن حالت رندر، در زمان استفاده از المان کامپوننت
<Banner @rendermode="@InteractiveAuto" Text="Hello"/>
البته به شرطی‌که using static زیر را به فایل Imports.razor_ پروژه اضافه کرد:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
مطالب
تولید فایل‌های اکسل حرفه‌ای بدون نیاز به نصب مجموعه‌ی آفیس

عموما بر روی سرورهای برنامه‌های وب، نرم افزار خاصی نصب نمی‌شود. برای مثال اگر نیاز به تولید فایل اکسل بر روی سرور باشد، سرور دار بعید است که آفیس را برای شما نصب کند و همچنین مایکروسافت هم این یک مورد را اصلا توصیه و پشتیبانی نمی‌کند (ایجاد چندین وهله از برنامه آفیس (تعامل با اشیاء COM) بر روی سرور توسط یک برنامه‌ی وب چند کاربره).
اگر سایت‌ها را هم جستجو کنید پر است از مقالاتی مانند تبدیل GridView به اکسل ... که تنها هنر آن‌ها انتخاب قسمت table مانند GridView و رندر کردن آن در مرورگر با پسوندی به نام xls یا xlsx است. به عبارتی فایل نهایی تولید شده استاندارد نیست. فقط یک html table است با پسوند xls/xlsx که برنامه‌ی اکسل می‌داند به چه صورتی باید آن‌را باز کند (که گاها در این بین فارسی سازی آن مشکل ساز می‌شود). این فایل نهایی تولیدی عاری است از امکانات پیشرفته‌ و حرفه‌ای اکسل. برای مثال اضافه کردن فرمول به آن، تبدیل اطلاعات به نمودارهای اکسل به صورت خودکار، داشتن فایلی با چندین work sheet‌ مختلف، اعمال قالب‌های مختلف، صفحه بندی بهتر و غیره.
مایکروسافت از سال 2007 تولید فایل‌های آفیس را با معرفی استاندارد OpenXML که توسط مؤسسه ایزو هم پذیرفته شده، بسیار ساده‌تر کرده است. OpenXML SDK‌ در دسترس است و توسط آن می‌توان فایل‌های اکسل حرفه‌ای را بدون نیاز به نصب مجموعه‌ی آفیس تولید کرد. کار کردن با OpenXML SDK هم در نگاه اول شاید ساده به نظر برسد اما آن هم ریزه کاری‌های خاص خودش را دارد که نمونه‌ای از آن‌را در مطلب "تولید فایل Word بدون نصب MS Word بر روی سرور" می‌توانید مشاهده کنید. به عبارتی این مجموعه جهت نوشتن کتابخانه‌های ویژه‌ی شما باز است ...
در این بین یکی از حرفه‌ای‌ترین کتابخانه‌هایی که امکانات تولید فایل‌های اکسل را به کمک OpenXML SDK‌ سهولت می‌بخشد، کتابخانه‌ی سورس باز EPPlus است:


مثالی در مورد نحوه‌ی استفاده از آن:
می‌خواهیم یک DataTable را به یک فایل اکسل واقعی (نه یک html table با پسوند xlsx) تبدیل کنیم با این شرایط که یکی از قالب‌های جدید آفیس به آن اعمال شود؛ جمع کل یکی از ستون‌ها توسط اکسل محاسبه گردیده و همچنین عرض دقیق ستون‌ها نیز در برنامه تنظیم گردد. نموداری نیز به صورت خودکار این اطلاعات را نمایش دهد:




using System.Data;
using System.IO;
using OfficeOpenXml;
using OfficeOpenXml.Drawing.Chart;
using OfficeOpenXml.Style;
using OfficeOpenXml.Table;

namespace EPPlusTest
{
class Program
{
static void Main(string[] args)
{
var newFile = new FileInfo("Test.xlsx");
if (newFile.Exists)
{
newFile.Delete();
}

//ایجاد یک سری اطلاعات دلخواه
var table = createDt();

using (var package = new ExcelPackage(newFile))
{
// اضافه کردن یک ورک شیت جدید
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("مخارج");

//اضافه کردن یک جدول جدید از دیتاتیبل دریافتی
worksheet.Cells["A1"].LoadFromDataTable(table, true, TableStyles.Dark9);

//نمایش جمع ستون هزینه‌های ماه‌ها
var tbl = worksheet.Tables[0];
//زیر آخرین ردیف یک سطر اضافه می‌کند
tbl.ShowTotal = true;
//فرمول نحوه‌ی محاسبه جمع ستون انتساب داده می‌شود
tbl.Columns[1].TotalsRowFunction = RowFunctions.Sum;

//تعیین عرض ستون‌های جدول
worksheet.Column(1).Width = 14;
worksheet.Column(2).Width = 12;

//تنظیم متن هدر
worksheet.HeaderFooter.oddHeader.CenteredText = "مثالی از نحوه‌ی استفاده از ایی پی پلاس";

//می‌خواهیم سرستون‌ها در وسط ستون قرار گیرند
worksheet.Cells["A1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
worksheet.Cells["B1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;

//افزودن یک نمودار جدید به شیت جاری
var chart = worksheet.Drawings.AddChart("chart1", eChartType.Pie3D);
chart.Title.Text = "نمودار هزینه‌های سال";
chart.SetPosition(Row: 2, RowOffsetPixels: 5, Column: 3, ColumnOffsetPixels: 5);
chart.SetSize(PixelWidth: 320, PixelHeight: 360);
chart.Series.Add("B2:B13", "A2:A13");
chart.Style = eChartStyle.Style26;

//تنظیم یک سری خواص فایل نهایی
package.Workbook.Properties.Title = "مثالی از ایی پی پلاس";
package.Workbook.Properties.Author = "وحید";
package.Workbook.Properties.Subject = "ایجاد فایل اکسل بدون نرم افزار اکسل";

//تنظیم نحوه‌ی نمایش فایل زمانیکه در نرم افزار اکسل گشوده می‌شود
worksheet.View.PageLayoutView = true;
worksheet.View.RightToLeft = true;

// ذخیر سازی کلیه موارد اعمالی در فایل
package.Save();
}
}

private static DataTable createDt()
{
var table = new DataTable("مخارج");
table.Columns.Add("ماه", typeof(string));
table.Columns.Add("هزینه", typeof(decimal));

table.Rows.Add("فروردین", 100);
table.Rows.Add("اردیبهشت", 250);
table.Rows.Add("خرداد", 80);
table.Rows.Add("تیر", 300);
table.Rows.Add("مرداد", 200);
table.Rows.Add("شهریور", 150);
table.Rows.Add("مهر", 250);
table.Rows.Add("آبان", 200);
table.Rows.Add("آذر", 400);
table.Rows.Add("دی", 100);
table.Rows.Add("بهمن", 130);
table.Rows.Add("اسفند", 80);
return table;
}
}
}

مطالب
آشنایی با ساختار IIS قسمت اول
در مقاله قبل در مورد نحوه ذخیره سازی در حافظه نوشتیم و به user mode و kernel mode اشاراتی کردیم که می‌توانید به آن رجوع کنید.
در این سری مقالات قصد داریم به بررسی اجزا و روند کاری موجود در IIS بپردازیم که چگونه IIS کار می‌کند و شامل چه بخش هایی می‌شود. مطمئنا آشنایی با این بخش‌ها در روند شناسایی رفتارهای وب اپلیکیشن‌ها و واکنش‌های سرور، کمک زیادی به ما خواهد کرد. در اینجا نسخه IIS7 را به عنوان مرجع در نظر گرفته‌ایم.
وب سرور IIS در عبارت مخفف Internet information services به معنی سرویس‌های اطلاعاتی اینترنت می‌باشد. IIS شامل کامپوننت‌های زیادی است که هر کدام ازآن‌ها کار خاصی را انجام میدهند؛ برای مثال گوش دادن به درخواست‌های ارسال شده به سرور، مدیریت فرآیندها Process و خواندن فایل‌های پیکربندی Configuration؛ این اجزا شامل protocol listener ،Http.sys و WSA و .. می‌شوند.
Protocol Listeners
این پروتکل‌ها به درخواست‌های رسیده گوش کرده و آن‌ها را مورد پردازش قرار می‌دهند و پاسخی را به درخواست کننده، ارسال می‌کنند. هر listener بر اساس نوع پروتکل متفاوت هست. به عنوان مثال کلاینتی، درخواست صفحه‌ای را می‌کند و http listener که به آن Http.sys می‌گویند به آن پاسخ می‌دهد. به طور پیش فرض http.sys به درخواست‌های http و https گوش فرا می‌دهد، این کامپوننت از IIS6 اضافه شده است ولی در نسخه 7 از SSL نیز پشتیبانی می‌کند.
Http.sys یا Hypertext transfer protocol stack
کار این واحد در سه مرحله دریافت درخواست، ارسال آن به واحد پردازش IIS و ارسال پاسخ به کلاینت است؛ قبل از نسخه 6 از Winsock یا windows socket api  که یک کامپوننت user-mod بود استفاده می‌شد ولی Http.sys یک کامپوننت Kernel-mod هست.

Http.sys مزایای زیر را به همراه دارد:

  • صف درخواست مد کرنل: به خاطر اینکه کرنل مستقیما درخواست‌ها را به پروسه‌های مربوطه میفرستد و اگر پروسه موجود نباشد، درخواست را در صف گذاشته تا بعدا پروسه مورد نظر آن را از صف بیرون بکشد.
  • برای درخواست‌ها یک پیش پردازش و همچنین اعمال فیلترهای امنیتی اعمال می‌گردد. 
  • عملیات کش کردن تماما در محیط کرنل مد صورت می‌گیرد؛ بدون اینکه به حالت یوزرمد سوییچ کند. مد کرنل دسترسی بسیار راحت و مستقیمی را برای استفاده از منابع دارد و لازم نیست مانند مد کاربر به لایه‌های زیرین، درخواست کاری را بدهد؛ چرا که خود مستقیما وارد عمل می‌شود و برداشته شدن واسط در سر راه، موجب افزایش عمل caching می‌شود. همچنین دسترسی به کش باعث می‌شود که مستقیما پاسخ از کش به کاربر برسد و توابع پردازشی در حافظه بارگذاری نشوند. البته این کش کردن محدودیت هایی را هم به همراه دارد:
    1. کش کرنل به صورت پیش فرض بر روی صفحات ایستا فعال شده است؛ نه برای صفحاتی با محتوای پویا که البته این مورد قابل تغییر است که نحوه این تغییر را پایینتر توضیح خواهیم داد.
    2. اگر آدرس درخواستی شامل کوئری باشد صفحه کش نخواهد شد:    http://www.site.info/postarchive.htm?id=25 
    3. برای پاسخ ازمکانیزم‌های فشرده سازی پویا استفاده شده باشد مثل gzip کش نخواهد شد
    4. صفحه درخواست شده صفحه اصلی سایت باشد کش نخواهد شد :   http://www.dotnettip.info ولی اگر درخواست بدین صورت باشه http://www.domain.com/default.htm  کش خواهد کرد.
    5. درخواست به صورت ناشناس anonymous نباشد  و نیاز به authentication داشته باشد کش نخواهد شد (یعنی در هدر شامل گزینه authorization می‌باشد).
    6. درخواست باید از نوع نسخه http1 به بعد باشد.
    7. اگر درخواست شامل Entity-body باشد کش نخواهد کرد.
    8. درخواست شامل If-Range/Range header باشد کش نمی‌شود.
    9. کل حجم response بییشتر از اندازه تعیین شده باشد کش نخواهد گردید، این اندازه در کلید ریجستری UriMaxUriBytes قرار دارد. اطلاعات بیشتر
    10. اندازه هدر بیشتر از اندازه تعیین شده باشد که عموما اندازه تعیین شده یک کیلو بایت است.
    11. کش پر باشد، کش انجام نخواهد گرفت.
    برای فعال سازی کش کرنل راهنمای زیر را دنبال کنید:
    گزینه output cache را در IIS، فعال کنید و سپس گزینه Add را بزنید. کادر add cache rule که باز شود، از شما میخواهد یکی از دو نوع کش مد کاربر و مد کرنل را انتخاب کنید و  مشخص کنید چه نوع فایل‌هایی (مثلا aspx) از این قوانین پیروری کنند و مکانیزم کش کردن به سه روش جلوگیری از کش کردن، کش زمان دار و کش بر اساس آخرین تغییر فایل انجام گردد.


    برای تعیین مقدار سایز کش response که در بالا اشاره کردیم می‌توانید در همان پنجره، گزینه edit feature settings را انتخاب کنید.


    این قسمت از مطلب که به نقل از مقاله  آقای Karol Jarkovsky در این آدرس است یک سری تست هایی با نرم افزار(Web Capacity Analysis Tool (WCAT  گرفته است که به نتایج زیر دست پیدا کرده است:
    Kernel Cache Disabled    4 clients/160 threads/30 sec      257 req/sec
    Kernel Cache Enabled     4 clients/160 threads/30 sec      553 req/sec 
    همانطور که می‌بینید نتیجه فعال سازی کش کرنل پاسخ به بیش از دو برابر درخواست در حالت غیرفعال آن است که یک عدد فوق العاده به حساب میاد.
    برای اینکه خودتان هم تست کرده باشید در این آدرس  برنامه را دانلود کنید و به دنبال فایل request.cfg بگردید و از صحت پارامترهای server و url اطمینان پیدا کنید. در گام بعدی 5 پنجره خط فرمان باز کرده و در یکی از آن‌ها دستور netsh http show cachestate را بنویسید تا تمامی وروردی‌های entry که در کش کرنل ذخیره شده اند لیست شوند. البته در اولین تست کش را غیرفعال کنید و به این ترتیب نباید چیزی نمایش داده شود. در همان پنجره فرمان wcctl –a localhost –c config.cfg –s request.cfg  را زده تا کنترلر برنامه در وضعیت listening قرار بگیرد. در 4 پنجره دیگر فرمان wcclient localhost از شاخه کلاینت را نوشته تا تست آغاز شود. بعد از انجام تست به شاخه نصب کنترلر WCAT رفته و فایل log را بخوانید و اگر دوباره دستور نمایش کش کرنل را بزنید باید خالی باشد. حالا کش را فعال کنید و دوباره عملیات تست را از سر بگیرید و اگر دستور netsh را ارسال کنید باید کش کرنل دارای ورودی باشد.
    برای تغییرات در سطح http.sys می‌توانید از ریجستری کمک بگیرید. در اینجا تعداد زیادی از تنظیمات ذخیره شده در ریجستری برای http.sys لیست شده است.