دریافت مثال های دوره
در صورت امکان مثالهای دوره رو اضاف نمایید
ممنون
سلام
چطور میشه در سایت dntips.ir پیشنهاد و نظر ارسال کرد؟ چون دسترسی بخش ارسال پیشنهاد به من داده نشده.
تشکر
برای آشنایی بیشتر با این helper توصیه میکنم ابتدا این مقاله را مطالعه نمایید.
به صورت پیش فرش WbebGrid صفحه بندی را به صورت خیلی ساده فقط با نمایش اعداد و جهت نماهای جلو و عقب، نشان میدهد که برای پروژههای رسمی تا حدودی جالب نیست.
در این مطلب قصد داریم از کتابخانهی bootstrap جهت صفحه بندی استفاده کنیم و در نهایت به صفحه بندی زیر برسیم:
برای اینکار، ابتدا قبل از هر چیزی به یک متد الحاقی برای انجام صفحه بندی سفارشی سازی شده، نیاز داریم که کدهای این متد به صورت زیر خواهد بود:
public static class WebGridExtensions { public static HelperResult PagerList( this WebGrid webGrid, WebGridPagerModes mode = WebGridPagerModes.NextPrevious | WebGridPagerModes.Numeric, string firstText = null, string previousText = null, string nextText = null, string lastText = null, int numericLinksCount = 5) { return PagerList(webGrid, mode, firstText, previousText, nextText, lastText, numericLinksCount, explicitlyCalled: true); } private static HelperResult PagerList( WebGrid webGrid, WebGridPagerModes mode, string firstText, string previousText, string nextText, string lastText, int numericLinksCount, bool explicitlyCalled) { int currentPage = webGrid.PageIndex; int totalPages = webGrid.PageCount; int lastPage = totalPages - 1; var ul = new TagBuilder("ul"); var li = new List<TagBuilder>(); if (ModeEnabled(mode, WebGridPagerModes.FirstLast)) { if (String.IsNullOrEmpty(firstText)) { firstText = "اولین"; } var part = new TagBuilder("li") { InnerHtml = GridLink(webGrid, webGrid.GetPageUrl(0), firstText) }; if (currentPage == 0) { part.MergeAttribute("class", "disabled"); } li.Add(part); } if (ModeEnabled(mode, WebGridPagerModes.NextPrevious)) { if (String.IsNullOrEmpty(previousText)) { previousText = "قبلی"; } int page = currentPage == 0 ? 0: currentPage - 1; var part = new TagBuilder("li") { InnerHtml = GridLink(webGrid, webGrid.GetPageUrl(page), previousText) }; if (currentPage == 0) { part.MergeAttribute("class", "disabled"); } li.Add(part); } if (ModeEnabled(mode, WebGridPagerModes.Numeric) && (totalPages > 1)) { int last = currentPage + (numericLinksCount / 2); int first = last - numericLinksCount + 1; if (last > lastPage) { first -= last - lastPage; last = lastPage; } if (first < 0) { last = Math.Min(last + (0 - first), lastPage); first = 0; } for (int i = first; i <= last; i++) { var pageText = (i + 1).ToString(CultureInfo.InvariantCulture); var part = new TagBuilder("li") { InnerHtml = GridLink(webGrid, webGrid.GetPageUrl(i), pageText) }; if (i == currentPage) { part.MergeAttribute("class", "active"); } li.Add(part); } } if (ModeEnabled(mode, WebGridPagerModes.NextPrevious)) { if (String.IsNullOrEmpty(nextText)) { nextText = "بعدی"; } int page = currentPage == lastPage ? lastPage: currentPage + 1; var part = new TagBuilder("li") { InnerHtml = GridLink(webGrid, webGrid.GetPageUrl(page), nextText) }; if (currentPage == lastPage) { part.MergeAttribute("class", "disabled"); } li.Add(part); } if (ModeEnabled(mode, WebGridPagerModes.FirstLast)) { if (String.IsNullOrEmpty(lastText)) { lastText = "آخرین"; } var part = new TagBuilder("li") { InnerHtml = GridLink(webGrid, webGrid.GetPageUrl(lastPage), lastText) }; if (currentPage == lastPage) { part.MergeAttribute("class", "disabled"); } li.Add(part); } ul.InnerHtml = string.Join("", li); var html = ""; if (explicitlyCalled && webGrid.IsAjaxEnabled) { var span = new TagBuilder("span"); span.MergeAttribute("data-swhgajax", "true"); span.MergeAttribute("data-swhgcontainer", webGrid.AjaxUpdateContainerId); span.MergeAttribute("data-swhgcallback", webGrid.AjaxUpdateCallback); span.InnerHtml = ul.ToString(); html = span.ToString(); } else { html = ul.ToString(); } return new HelperResult(writer => { writer.Write(html); }); } private static String GridLink(WebGrid webGrid, string url, string text) { TagBuilder builder = new TagBuilder("a"); builder.SetInnerText(text); builder.MergeAttribute("href", url); if (webGrid.IsAjaxEnabled) { builder.MergeAttribute("data-swhglnk", "true"); } return builder.ToString(TagRenderMode.Normal); } private static bool ModeEnabled(WebGridPagerModes mode, WebGridPagerModes modeCheck) { return (mode & modeCheck) == modeCheck; } }
پس از آن در View یی که اطلاعات را نمایش میدهید، فقط لازم است کد زیر را اضافه نمایید:
<div> @grid.PagerList(mode: WebGridPagerModes.All) </div>
در ادامه برای تکمیل بحث مثالی را از نحوهی نمایش اطلاعات و صفحه بندی سفارشی نشان خواهیم داد:
PartialView لازم برای نمایش اطلاعات
تنظیمات لازم گرید :
@{ WebGrid grid = new WebGrid(Model, rowsPerPage: 10, ajaxUpdateContainerId: "grid"); var rowIndex = ((grid.PageIndex + 1) * grid.RowsPerPage) - (grid.RowsPerPage - 1); }
@grid.Table( tableStyle: "table table-striped table-hover", headerStyle: "webgrid-header", alternatingRowStyle: "webgrid-alternating-row", selectedRowStyle: "webgrid-selected-row", rowStyle: "webgrid-row-style", columns: grid.Columns( grid.Column(columnName: "Name", header: "نام استان", style: "myfont"), grid.Column(columnName: "NameEn", header: "نام استان ( انگلیسی )", style: "myfont"), grid.Column(header: "", format: item => @Html.ActionLink("مدیریت شهرها", actionName: MVC.Admin.City.ActionNames.Index, controllerName: MVC.Admin.City.Name, routeValues: new {Code=item.Code },htmlAttributes:null)), grid.Column(header: "", style: "text-align-center-col smallcell", format: item => @Html.ActionLink(linkText: "ویرایش", actionName: "Edit", controllerName: "Province", routeValues: new { area = "Admin", code = item.Code }, htmlAttributes: new { @class = "btn-sm btn-info vertical-center" })) ) ) <div> @grid.PagerList(mode: WebGridPagerModes.All) </div>
خروجی حاصل به صورت زیر خواهد بود :
اگر طبق توضیحات بالا عمل کرده باشید، در نهایت صفحه بندی شما به صورت عمودی نمایش داده میشود؛ یعنی هر کدام از شماره صفحات در یک سطر. دلیل آن هم این است که تگ ul، کلاس .pagination را ندارد. در کدهای بوت استراپ تعریف شده است که تمام li هایی که به صورت مستقیم داخل کلاس .pagination هستند خصوصیات مورد نظر را بگیرند.
برای این کار دو راه حل وجود دارد :
راه حل اول: تغییر کدهای css
کدهای نوشته شده برای صفحه بندی در بوت استراپ را از حالت زیر:
.pagination > li
.pagination li
یادآوری : علامت < در CSS یعنی به صورت مستقیم و در شاخهی اول.
راه حل دوم - افزودن کلاس .pagination به تگ ul:
ابتدا کلاس .pagination را از تگ div حذف نمایید:
<div > @grid.PagerList(mode: WebGridPagerModes.All) </div>
و در کدهایی کلاس WebGridExtensions، در قسمتی که تگ ul اصافه میشود، کلاس مورد نظر را به آن اضافه میکنیم:
var ul = new TagBuilder("ul"); ul.AddCssClass("pagination");
دانلود کدهای این مثال
نکتهای در مورد Webgrid
اگر نیاز داشتید به یکباره تمام اطلاعات را در گرید لود نکنید و به صورت n تاn تا رکوردها را نمایش دهید، در این حالت پس از پاس دادن لیستی از اطلاعات به View مورد نظر لازم است تعداد کل رکوردها را در یک متغییر به سمت View بفرستید. این کار به این دلیل میباشد که بتوان صفحه بندی را تولید کرد. برای این کار در بخش تنظیمات Webgrid مقدار source را برابر null قرار دهید و از قطعه کد زیر جهت بایند کردن گرید، بعد از کدهای تنظیمات WebGrid استفاده نمایید:
grid.Bind(Model, rowCount: (int)ViewBag.PageCount);
EF Code First #6
متد بالا رو برای DataBase First هم میشه استفاده کرد؟
1. شاید یکی از آزاردهندهترین مشکلات، برخورد با پیغامهای خطا، هنگام عملیات migration باشد. یکی از دهها نوع خطا، زمانی رخ میدهد که متد seed در حال اجراست. در این حالت هیچ نوع break-point ایی به کمک ما نخواهد آمد.
سوال ایجاست که آیا میتوان این بخش را دیباگ نمود؟ بهترین راه حل، اجرای آپدیت از طریق متدها(یا اکشن ها) است.
فراخوانی migration بسیار ساده است. باید یک نمونه از کلاس Configuration را ساخته و در جایی از پروژه قرار دهیم و صد
البته مطمئن باشیم که migration فعال است.
var configuration = new Configuration(); var migrator = new DbMigrator(configuration); migrator.Update();
اگر بخواهید این تغییرات بر روی دیتابیسی با اسم و رسم انجام شود، از کد زیر بهره بگیرید:
var configuration = new Configuration(); configuration.TargetDatabase = new DbConnectionInfo( "Server=MyServer;Database=MyDatabase;Trusted_Connection=True;", "System.Data.SqlClient"); var migrator = new DbMigrator(configuration); migrator.Update();
2. خطای :
Duplicate type name within an assembly.
معمولا بخاطر وجود break-point این مشکل رخ میدهد. یا break-pointهای درون seed را حذف کنید یا جای آنها را تغییر دهید. [اینجا]
3. خطای :
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
این مشکل میتواند دلایل مختلفی داشته
باشد. کد درون متد seed را داخلtry/catch قرا ر دهید و علت آن را بررسی کنید:
try { var user = new ApplicationUser { UserName = "Admin", Phone = "09120000000", Email = "m@gmail.com" }; usermanager.Create(user, "09120000000"); usermanager.AddToRole(user.Id, "admin"); } catch (DbEntityValidationException dbEx) { foreach (var validationErrors in dbEx.EntityValidationErrors) { foreach (var validationError in validationErrors.ValidationErrors) { Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); } } }
فراموش نکنید، seed را به کمک حالتی که در شماره 1 گفته شد اجرا کنید تا بتوانید از BreakPoint استفاده کنید.
4.خطای :
More than one context type was found in the assembly 'Ex1_CodeFirst'
این خطا وقتی ظاهر میشود که چندین Context برای
پروژه جاری داشته باشیم و ویژوال استودیو نتواند Context مورد نظر ما را تشخیص
دهد. بهتر است هنگام اجرای migration نام context مورد نظر را بنویسیم (مثلا MyConfiguration نام کانتکست شماست) :
Update-Database -ConfigurationTypeName MyConfiguration
There is already an object named 'UserProfile' in the database.
یعنی مدل شما پس از اینکه جدولی با همین نام (در این جا به عنوان مثال UserProfile) از پیش موجود بوده، تغییر کرده است؛ پس migration آپدیت شدنی نیست. ابتدا این دستور را مینویسیم (پیش از آنکه تغییراتمان را روی کلاس مربوط به جدول UserProfile اعمال کنیم):
Add-Migration Initial –IgnoreChanges
update-database –verbose
گاهی این مشکل زمانی پیش میآید که واقعا تغییری در جدول نامبرده انجام ندادهایم. در این حالت روش پلهای زیر را به کار میبریم:
- پاک کردن یا ریست کردن migration (در شماره 9 همین مقاله این کار در چند مرحله توضیح داده شده است. در مرحله سوم حتما از Add-Migration Initial –IgnoreChanges استفاده کنید)
- بعد از آپدیت دیتابیس باید فایل زیر دارای محتویات باشد
محتویات این فایل دقیقا شرایط فعلی جدول شماست.
- اگر این فایل ایجاد نشده است مراحل را تکرار کنید تا محتویات درون آن را ببینید.
- حالا تغییری را در یکی از مدلهای خود انجام دهید. احتمالا با مشکل آپدیت شدن مواجه میشوید و پیغام زیر را دوباره خواهید دید:
There is already an object named 'UserProfile' in the database
- جدولی را که پیغام خطا به آن اشاره کرده، در فایل فوق بیابید و محدوده create آن را کامنت کنید تا ساخته نشود.
- دیتابیس را آپدیت کنید، احتمالا پیغام خطای فوق برای جدول دیگری نمایش داده میشود. آن را هم کامنت کنید و دیتابیس را آپدیت کنید و اگر باز هم خطا بود مکانیزم بالا را تا جایی تکرار کنید که خطایی نبینید .
- حالا جدولی را که تغییراتی در آن داده بودید، در دیتابیس چک کنید که تغییرات اعمال شده باشد.
- هر آنچه را در فایل initial کامنت کرده بودید، از کامنت خارج کنید و دیتابیس را آپدیت کنید .
- برای آزمایش، یک آیتم به یکی از مدلها اضافه کنید و ببینید که migration درست کار میکند یا خیر.
Unable to update database to match the current model because there are pending changes and automatic migration is disabled. Either write the pending model changes to a code-based migration or enable automatic migration. Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true to enable automatic migration. You can use the Add-Migration command to write the pending model changes to a code-based migration.
7.خطای :
Automatic migration was not applied because it would result in data loss.
AutomaticMigrationDataLossAllowed = true;
Introducing FOREIGN KEY constraint 'FK_dbo.ProductProductGroups_dbo.ProductGroups_ProductGroupId' on table 'ProductProductGroups' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint or index. See previous errors.
9. راه حل برای خطاهای عجیبی که شاید نیاز به صرف زمان بیشتر برای کشف و برطرف کردن داشته باشند :
بهتر نیست مایگریشن خود را از نو بسازید؟
روش به روز رسانی و بازسازی migration [اینجا ]:
- پاک کردن فولدر Migrations در پروژه
- پاک کردن جدولی به نام MigrationHistory_ در دیتابیس (ممکن است زیر مجموعه جدولهای system باشد)
- اجرای دستور زیر کنسول پکیچ منیجر ویژوال استودیو :
Enable-Migrations -EnableAutomaticMigrations -Force
- اجرای دستور زیر :
Add-Migration Initial
ASP.NET MVC #8
معرفی HTML Helpers
یک HTML Helper تنها یک متد است که رشتهای را بر میگرداند و این رشته میتواند حاوی هر نوع محتوای دلخواهی باشد. برای مثال میتوان از HTML Helpers برای رندر تگهای HTML، مانند img و input استفاده کرد. یا به کمک HTML Helpers میتوان ساختارهای پیچیدهتری مانند نمایش لیستی از اطلاعات دریافت شده از بانک اطلاعاتی را پیاده سازی کرد. به این ترتیب حجم کدهای تکراری تولید رابط کاربری در Viewهای برنامههای ASP.NET MVC به شدت کاهش خواهد یافت، به همراه قابلیت استفاده مجدد از متدهای الحاقی HTML Helpers در برنامههای دیگر.
HTML Helpers در ASP.NET MVC معادل کنترلهای ASP.NET Web forms هستند اما نسبت به آنها بسیار سبکتر میباشند؛ برای مثال به همراه ViewState و همچنین Event model نیستند.
ASP.NET MVC به همراه تعدادی متد HTML Helper توکار است و برای دسترسی به آنها شیء Html که وهلهای از کلاس توکار HtmlHelper میباشد، در تمام Viewها قابل استفاده است.
نحوه ایجاد یک HTML Helper سفارشی
از دات نت سه و نیم به بعد امکان توسعه اشیاء توکار فریم ورک، به کمک متدهای الحاقی (extension methods) میسر شده است. برای نوشتن یک HTML Helper نیز باید همین شیوه عمل کرد و کلاس HtmlHelper را توسعه داد. در ادامه قصد داریم یک HTML Helper را جهت رندر تگ label در صفحه ایجاد کنیم. برای این منظور پوشهی جدیدی به نام Helper را به پروژه اضافه نمائید (جهت نظم بیشتر). سپس کلاس زیر را به آن اضافه کنید:
using System;
using System.Web.Mvc;
namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static string MyLabel(this HtmlHelper helper, string target, string text)
{
return string.Format("<label for='{0}'>{1}</label>", target, text);
}
}
}
همانطور که ملاحظه میکنید متد Label به شکل یک متد الحاقی توسعه دهنده کلاس HtmlHelper که تنها یک رشته را بر میگرداند، تعریف شده است. اکنون برای استفاده از این متد در View دلخواهی خواهیم داشت:
@using MvcApplication4.Helpers
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
@Html.MyLabel("firstName", "First Name:")
ابتدا فضای نام مرتبط با متد الحاقی باید پیوست شود و سپس از طریق شیء Html میتوان به این متد الحاقی دسترسی پیدا کرد. اگر برنامه را اجرا کنید، این خروجی را مشاهده خواهیم کرد. چرا؟
Index
<label for='firstName'>First Name:</label>
علت این است که Razor، اطلاعات را Html encoded به مرورگر تحویل میدهد. برای تغییر این رویه باید اندکی متد الحاقی تعریف شده را تغییر داد:
using System.Web.Mvc;
namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyLabel(this HtmlHelper helper, string target, string text)
{
return MvcHtmlString.Create(string.Format("<label for='{0}'>{1}</label>", target, text));
}
}
}
تنها تغییر صورت گرفته، استفاده از MvcHtmlString بجای string معمولی است تا Razor آنرا encode نکند.
تعریف HTML Helpers سفارشی به صورت عمومی:
میتوان فضای نام MvcApplication4.Helpers این مثال را عمومی کرد. یعنی بجای اینکه بخواهیم در هر View آنرا ابتدا تعریف کنیم، یکبار آنرا همانند تعاریف اصلی یک برنامه ASP.NET MVC، عمومی معرفی میکنیم. برای این منظور فایل web.config موجود در پوشه Views را باز کنید (و نه فایل web.config قرار گرفته در ریشه اصلی برنامه). سپس فضای نام مورد نظر را در قسمت namespaces صفحات اضافه نمائید:
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="MvcApplication4.Helpers"/>
</namespaces>
به این ترتیب متدهای الحاقی تعریف شده در فضای نام MvcApplication4.Helpers، در تمام Viewهای برنامه در دسترس خواهند بود.
استفاده از کلاس TagBuilder برای تولید HTML Helpers سفارشی:
using System.Web.Mvc;
namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyNewLabel(this HtmlHelper helper, string target, string text)
{
var labelTag = new TagBuilder("label");
labelTag.MergeAttribute("for", target);
labelTag.InnerHtml = text;
return MvcHtmlString.Create(labelTag.ToString());
}
}
}
در فضای نام System.Web.Mvc، کلاسی وجود دارد به نام TagBuilder که کار تولید تگهای HTML، مقدار دهی ویژگیها و خواص آنها را بسیار ساده میکند و روش توصیه شدهای است برای تولید متدهای HTML Helper. یک نمونه از کاربرد آنرا برای بازنویسی متد MyLabel ذکر شده در اینجا ملاحظه میکنید.
شبیه به همین کلاس، کلاس دیگری به نام HtmlTextWriter در فضای نام System.Web.UI برای انجام اینگونه کارها وجود دارد.
نوشتن HTML Helpers ویژه، به کمک امکانات Razor
نوع دیگری از این متدهای کمکی، Declarative HTML Helpers نام دارند. از این جهت هم Declarative نامیده شدهاند که مستقیما درون فایلهای cshtml یا vbhtml به کمک امکانات Razor قابل تعریف هستند. تولید این نوع متدهای کمکی به این شکل نسبت به مثلا روش TagBuilder سادهتر است، چون توسط Razor به سادگی و به نحو طبیعیتری میتوان تگهای HTML و کدهای مورد نظر را با هم ترکیب کرد (این رفتار طبیعی و روان، یکی از اهداف Razor است).
به عنوان مثال، تعاریف همان کلاسهای Product و Products قسمت قبل (قسمت هفتم) را در نظر بگیرید. با همان کنترلر و View ایی که ذکر شد.
سپس برای تعریف این نوع خاص از HTML Helpers/Razor Helpers باید به این نحو عمل کرد:
الف) در ریشه پروژه یا سایت، پوشهی جدیدی به نام App_Code ایجاد کنید (دقیقا به همین نام. این پوشه، جزو پوشههای ویژه ASP.NET است).
ب) بر روی این پوشه کلیک راست کرده و گزینه Add|New Item را انتخاب کنید.
ج) در صفحه باز شده، MVC 3 Partial Page/Razor را یافته و مثلا نام ProductsList.cshtml را وارد کرده و این فایل را اضافه کنید.
د) محتوای این فایل جدید را به نحو زیر تغییر دهید:
@using MvcApplication4.Models
@helper GetProductsList(List<Product> products)
{
<ul>
@foreach (var item in products)
{
<li>@item.Name ($@item.Price)</li>
}
</ul>
}
در اینجا نحوه تعریف یک helper method مخصوص Razor را مشاهده میکنید که با کلمه @helper شروع شده است. مابقی آن هم ترکیب آشنای code و markup هستند که به کمک امکانات Razor به این شکل روان میسر شده است.
اکنون اگر Viewایی بخواهد از این اطلاعات استفاده کند تنها کافی است به نحو زیر عمل نماید:
@model List<MvcApplication4.Models.Product>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
@ProductsList.GetProductsList(@Model)
ابتدا نام فایل ذکر شده بعد نام متد کمکی تعریف شده در آن. Model هم در اینجا به لیستی از محصولات اشاره میکند.
همچنین چون در پوشه app_code قرار گرفته، تمام Viewها به اطلاعات آن دسترسی خواهند داشت. علت هم این است که ASP.NET به صورت خودکار محتوای این پوشه ویژه را همواره کامپایل میکند و در اختیار برنامه قرار میدهد.
به علاوه در این فایل ProductsList.cshtml، باز هم میتوان متدهای helper دیگری را اضافه کرد و از این بابت محدودیتی ندارد. همچنین میتوان این متد helper را مستقیما داخل یک View هم تعریف کرد. بدیهی است در این حالت قابلیت استفاده مجدد از آنرا به همراه داشتن Viewهایی تمیز و کم حجم، از دست خواهیم داد.
جهت تکمیل بحث
Turn your Razor helpers into reusable libraries
اگر ویژگیهای پیشفرض مهیا، پاسخگوی اعتبارسنجی مدنظر نبودند، میتوان یک attribute سفارشی را تهیه کرد:
using System.ComponentModel.DataAnnotations; namespace CustomValidators { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] public class EmailDomainValidator : ValidationAttribute { public string AllowedDomain { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { string[] strings = value.ToString().Split('@'); if (strings[1].ToUpper() == AllowedDomain.ToUpper()) { return null; } return new ValidationResult($"Domain must be {AllowedDomain}", new[] { validationContext.MemberName }); } } }
- کار با ارث بری از کلاس پایهی ValidationAttribute شروع میشود و باید متد IsValid آنرا بازنویسی کرد.
- اگر متد IsValid، نال برگرداند، یعنی مشکلی نیست؛ در غیراینصورت خروجی آن باید از نوع ValidationResult باشد.
- پارامتر validationContext اطلاعاتی مانند نام خاصیت در حال بررسی را ارائه میدهد.
- در اینجا متد ()ValidationContext.GetService نال را بر میگرداند؛ یعنی فعلا از تزریق وابستگیها در آن پشتیبانی نمیشود.
و در آخر روش استفادهی از آن، همانند سایر ویژگیهای اعتبارسنجی است:
public class Employee { [EmailDomainValidator(AllowedDomain = "site.com")] public string Email { get; set; } }
نمایش قسمتی از صفحه بر اساس وضعیت اعتبارسنجی کاربر
فرض کنید میخواهیم در کامپوننت Shared\LoginDisplay.razor که در قسمت قبل آنرا اضافه کردیم، لینکهای ثبت نام و لاگین را به کاربران غیر اعتبارسنجی شده (هنوز لاگین نکرده) نمایش دهیم و اگر کاربر، اعتبارسنجی شده بود (لاگین کرده بود)، لینک خروج را به او نمایش دهیم. برای این منظور کامپوننت Shared\LoginDisplay.razor را به صورت زیر تغییر میدهیم:
<AuthorizeView> <Authorized> <a href="Identity/Account/Logout">Logout</a> </Authorized> <NotAuthorized> <a href="Identity/Account/Register">Register</a> <a href="Identity/Account/Login">Login</a> </NotAuthorized> </AuthorizeView>
البته اگر برنامه را در همین حالت اجرا کنیم، به استثنای زیر خواهیم رسید:
InvalidOperationException: Authorization requires a cascading parameter of type Task<AuthenticationState>. Consider using CascadingAuthenticationState to supply this. Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync()
بنابراین به فایل BlazorServer.App\App.razor که محل تعریف ریشهی مسیریابی برنامهاست، مراجعه کرده و کامپوننت آنرا با کامپوننت توکار CascadingAuthenticationState محصور میکنیم:
<CascadingAuthenticationState> <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState>
اکنون اگر برنامه را اجرا کنیم، مشاهده خواهیم کرد که در اولین بار مراجعهی به آن (پیش از لاگین)، لینک به صفحهی خروج، نمایش داده نشدهاست؛ چون آنرا در فرگمنت مخصوص Authorized قرار دادیم:
آزمایش نمایش منوی خروج برنامه
برای آزمایش برنامه، نیاز است ابتدا یک کاربر جدید را ثبت کنیم؛ چون هنوز هیچ کاربری در آن ثبت نشدهاست و همچنین کاربر پیشفرضی را هم به همراه ندارد. در مورد روش ثبت کاربران پیشفرض ASP.NET Core Identity، میتوانید به مطلب «بازنویسی متد مقدار دهی اولیهی کاربر ادمین در ASP.NET Core Identity توسط متد HasData در EF Core» مراجعه کنید و تمام نکات آن، در اینجا هم صادق است (چون پایهی سیستم Identity مورد استفاده، یکی است و هدف ما در اینجا بیشتر بررسی نکات یکپارچه سازی آن با Blazor Server است و نه مرور تمام نکات ریز Identity).
بنابراین ابتدا از منوی بالای صفحه، گزینهی Register را انتخاب کرده و کاربری را ثبت میکنیم. پس از ثبت نام، بلافاصله به منوی جدید زیر میرسیم که در آن گزینههای ورود و ثبت نام، مخفی شدهاند و اکنون گزینهی خروج از سیستم را نمایش میدهد:
بهبود تجربهی کاربری خروج از سیستم
در همین حال که گزینهی خروج نمایش داده شدهاست، اگر بر روی لینک آن کلیک کنیم، ابتدا ما را به صفحهی مجزای logout هدایت میکند. سپس باید در این صفحه، مجددا بر روی لینک logout بالای آن کلیک کنیم. زمانیکه اینکار را انجام دادیم، اکنون صفحهی دیگری را نمایش میدهد که به همراه پیام «خروج موفقیت آمیز از سیستم» است! در این پروسه، کاربر احساس میکند که کاملا از برنامهی اصلی خارج شدهاست و همچنین مراحل طولانی را نیز باید طی کند.
مدیریت این مراحل توسط دو فایل زیر انجام میشوند:
Areas\Identity\Pages\Account\Logout.cshtml
Areas\Identity\Pages\Account\Logout.cshtml.cs
میخواهیم کدهای این دو فایل را به نحوی تغییر دهیم که اگر کاربری بر روی لینک logout برنامهی اصلی کلیک کرد، به صورت خودکار logout شده و سپس مجددا به صفحهی اصلی برنامهی Blazor Server هدایت شود و مجبور نباشد تا مراحل طولانی یاد شده را تکرار کند.
به همین جهت ابتدا فایل Logout.cshtml.cs را حذف میکنیم؛ چون نیازی به آن نداریم. سپس محتوای فایل Logout.cshtml را به صورت زیر تغییر میدهیم:
@page @using Microsoft.AspNetCore.Identity @inject SignInManager<IdentityUser> SignInManager @functions { public async Task<IActionResult> OnGet() { if (SignInManager.IsSignedIn(User)) { <p>You have successfully logged out of the application.</p> await SignInManager.SignOutAsync(); } return Redirect("~/"); } }
نمایش User Claims، در یک برنامهی Blazor Server
سیستم ASP.NET Core Identity، بر اساس User Claims کار میکند؛ اطلاعات بیشتر. پس از استفاده از CascadingAuthenticationState در بالاترین سطح برنامه، اطلاعات آن در سراسر برنامهی Blazor Server هم قابل دسترسی است. برای مثال در کامپوننت Shared\LoginDisplay.razor، به نحو زیر میتوان نام کاربر ثبت نام شده را که یکی از User Claims او است، نمایش داد:
<AuthorizeView> <Authorized> Hello, @context.User.Identity.Name <a href="Identity/Account/Logout">Logout</a> </Authorized>
محدود کردن دسترسی به صفحات برنامه تنها برای کاربران اعتبارسنجی شده
پس از لاگین موفق به سیستم، اکنون میخواهیم دسترسی به صفحات تعریف اتاقها و یا امکانات رفاهی هتل را تنها به کاربران لاگین شده، محدود کنیم. برای اینکار تنها کافی است از ویژگی Authorize استفاده کنیم. برای مثال به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و یک سطر زیر را به آن اضافه میکنیم:
@attribute [Authorize]
مشکل! با اینکه تمام کامپوننتهای مثال جاری را به ویژگی Authorize مزین کردهایم، اما ... کار نمیکند! و هنوز هم میتوان بدون لاگین به سیستم، به محتوای آنها دسترسی داشت.
برای رفع این مشکل، مجددا نیاز است کامپوننت BlazorServer.App\App.razor را ویرایش کرد:
<CascadingAuthenticationState> <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true"> <Found Context="routeData"> @*<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />*@ <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> <NotAuthorized> <p>Sorry, you do not have access to this page</p> </NotAuthorized> </AuthorizeRouteView> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-22.zip