@{ var token = await HttpContext.GetTokenAsync("access_token"); } <component type="typeof(App)" render-mode="ServerPrerendered" param-AccessToken="token" />
<CascadingValue Name="AccessToken" Value="AccessToken"> <CascadingAuthenticationState> <Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState> </CascadingValue> @code{ [Parameter] public string AccessToken { get; set; } }
@page "/showtoken" <p>This is part of the access token @(AccessToken != null ? AccessToken.Substring(0,30) : "(null)")</p> @code { [CascadingParameter(Name = "AccessToken")] public string AccessToken { get; set; } }
var userName = await HttpContext.User.Identity.Name;
var khasCookie = HttpContext.Request.Cookies["khas"];
نمایش قسمتی از صفحه بر اساس وضعیت اعتبارسنجی کاربر
فرض کنید میخواهیم در کامپوننت 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
اگر چک لیستهای SEO وب سایت ها را مشاهده کنیم، میتوانیم آنها را در دو دستهی کلی بهینه سازی درونی و برونی وب سایت در نظر بگیریم:
Off-Page Optimization یا برونی ، که بیشتر بر دوش مشاوران سئو و خود مدیران وب سایت است.(link building ، فعالیت در شبکه اجتماعی و ...)
و اما در حوزه On-Page Optimization یا درونی که بخشهای مهمی از آن وظیفهی مابرنامه نویسها است.(H1 Tag ، URL Naming ، Meta Tags ، عنوان صفحه و ...)
[البته عامل درونی بهینه سازی محتوا (Content Optimization) که مهمترین عامل در الگوریتمهای نسل جدید موتورهای جستجو و همچنین الگوریتم جدید گوگل (و +) به حساب میآید بر عهده مشاوران سئو و خود مدیران وب سایت میباشد]
در ادامه به ارائه چند راهکار جهت بهینه سازی برنامههای وب ASP.NET مان برای موتورهای جستجو میپردازیم:
1.متدی برای ایجاد عنوان سایت
private const string SeparatorTitle = " - "; private const int MaxLenghtTitle = 60; public static string GeneratePageTitle(params string[] crumbs) { var title = ""; for (int i = 0; i < crumbs.Length; i++) { title += string.Format ( "{0}{1}", crumbs[i], (i < crumbs.Length - 1) ? SeparatorTitle : string.Empty ); } title = title.Substring(0, title.Length <= MaxLenghtTitle ? title.Length : MaxLenghtTitle).Trim(); return title; }
- MaxLenghtTitle پیشنهادی برای عنوان سایت 60 میباشد.
2.متدی برای ایجاد متاتگ صفحات سایت
public enum CacheControlType { [Description("public")] _public, [Description("private")] _private, [Description("no-cache")] _nocache, [Description("no-store")] _nostore }
private const int MaxLenghtTitle = 60; private const int MaxLenghtDescription = 170; private const string FaviconPath = "~/cdn/ui/favicon.ico"; public static string GenerateMetaTag(string title, string description, bool allowIndexPage, bool allowFollowLinks, string author = "", string lastmodified = "", string expires = "never", string language = "fa", CacheControlType cacheControlType = CacheControlType._private) { title = title.Substring(0, title.Length <= MaxLenghtTitle ? title.Length : MaxLenghtTitle).Trim(); description = description.Substring(0, description.Length <= MaxLenghtDescription ? description.Length : MaxLenghtDescription).Trim(); var meta = ""; meta += string.Format("<title>{0}</title>\n", title); meta += string.Format("<link rel=\"shortcut icon\" href=\"{0}\"/>\n", FaviconPath); meta += string.Format("<meta http-equiv=\"content-language\" content=\"{0}\"/>\n", language); meta += string.Format("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/>\n"); meta += string.Format("<meta charset=\"utf-8\"/>\n"); meta += string.Format("<meta name=\"description\" content=\"{0}\"/>\n", description); meta += string.Format("<meta http-equiv=\"Cache-control\" content=\"{0}\"/>\n", EnumExtensions.EnumHelper<CacheControlType>.GetEnumDescription(cacheControlType.ToString())); meta += string.Format("<meta name=\"robots\" content=\"{0}, {1}\" />\n", allowIndexPage ? "index" : "noindex", allowFollowLinks ? "follow" : "nofollow"); meta += string.Format("<meta name=\"expires\" content=\"{0}\"/>\n", expires); if (!string.IsNullOrEmpty(lastmodified)) meta += string.Format("<meta name=\"last-modified\" content=\"{0}\"/>\n", lastmodified); if (!string.IsNullOrEmpty(author)) meta += string.Format("<meta name=\"author\" content=\"{0}\"/>\n", author); //------------------------------------Google & Bing Doesn't Use Meta Keywords ... //meta += string.Format("<meta name=\"keywords\" content=\"{0}\"/>\n", keywords); return meta; }
-
MaxLenghtDescription پیشنهادی برای متاتگ توضیح سایت 170 می باشد.
- آشنایی با متاتگها (Meta tags) و کاربرد آنها در صفحات وب (HTML)
- برای کاربرد allowIndexPage و allowFollowLinks هم میتوانید به لینک بالا و بررسی متاتگ robots بپردازید.
- با توجه به اهمیت شبکههای اجتماعی متاتگهای شبکههای اجتماعی (+ و +) را هم نباید از قلم انداخت.
- برای دریافت Description نوع سفارشی CacheControlType از پروژه متدهای الحاقی علیرضا اسم رام استفاده کردم.
3.متدی برای ایجاد Slug ( اسلاگ آدرسی با مفهوم برای بکار بردن در URL ها است که دوستدار موتورهای جستجو میباشد)
private const int MaxLenghtSlug = 45; public static string GenerateSlug(string title) { var slug = RemoveAccent(title).ToLower(); slug = Regex.Replace(slug, @"[^a-z0-9-\u0600-\u06FF]", "-"); slug = Regex.Replace(slug, @"\s+", "-").Trim(); slug = Regex.Replace(slug, @"-+", "-"); slug = slug.Substring(0, slug.Length <= MaxLenghtSlug ? slug.Length : MaxLenghtSlug).Trim(); return slug; } private static string RemoveAccent(string text) { var bytes = Encoding.GetEncoding("UTF-8").GetBytes(text); return Encoding.UTF8.GetString(bytes); }
- MaxLenghtSlug پیشنهادی برای عنوان سایت 45 میباشد.
نمونه ای از کاربرد توابع :
Head.InnerHtml = SEO.GenerateMetaTag ( title: SEO.GeneratePageTitle(".NET Tips", "آرشیو مطالب", "ASP.NET MVC #1"), description: "چرا ASP.NET MVC با وجود فریم ورک پختهای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح میشود این است: «برای چی؟». بنابراین تا به این سؤال پاسخ داده نشود، هر نوع بحث فنی در این مورد بی فایده است.", allowIndexPage: true, allowFollowLinks: true, author: "وحید نصیری", cacheControlType: SEO.CacheControlType._private );
<title>.NET Tips - آرشیو مطالب - ASP.NET MVC #1</title> <link rel="shortcut icon" href="../../cdn/images/ui/favicon.ico"/> <meta http-equiv="content-language" content="fa"/> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <meta charset="utf-8"/> <meta name="description" content="چرا ASP.NET MVC ؟با وجود فریم ورک پختهای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح میشود این است: «برای چی؟». بن ..."/> <meta http-equiv="Cache-control" content="private"/> <meta name="robots" content="index, follow" /> <meta name="expires" content="never"/> <meta name="author" content="وحید نصیری"/>
name: Build on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 - name: Build (Release) run: dotnet build src --configuration Release - name: Pack (Release) run: dotnet pack src --configuration Release - name: Upload Artifact uses: actions/upload-artifact@v1.0.0 with: name: Release_Artifacts path: src\bin\Release
- عبارت src به پوشه پروژه اصلی اشاره میکند. (در صورت نیاز آن را تغییر دهید)
- عبارت "Release_Artifacts" نام فایل Artifacts خروجی شماست که با این نام ذخیره میشود.
- مسیر "src\bin\Release" پوشه ای است که میخواهید محتوای آن را در Artifacts ذخیره کنید.
var physicalPath = Path.Combine(Server.MapPath("~/Content/Images"), fileName);
public static class PathConverter { public static string PhysicalToVirtualPathConverter(this HttpServerUtilityBase utility, string path, HttpRequestBase context) { return path.Replace(context.ServerVariables["APPL_PHYSICAL_PATH"], "/").Replace(@"\", "/"); } }
image.ImagePath = Server.PhysicalToVirtualPathConverter(PhysicalPath, Request);
ایجاد فرمهای مقدماتی، با بوت استرپ 4
بوت استرپ به همراه کلاسهایی مانند form-group و form-control است که از آنها میتوان برای ایجاد یک فرم مقدماتی استفاده کرد. در ابتدا مثال غیر تزئین شدهی زیر را در نظر بگیرید:
<body> <div class="container"> <h2>Medical Questionnaire</h2> <form> <fieldset> <legend>Owner Info</legend> <div> <label for="ownername">Owner name</label> <input type="text" id="ownername" placeholder="Your Name"> </div> <div> <label for="owneremail">Email address</label> <input type="email" id="owneremail" aria-describedby="emailHelp" placeholder="Enter email"> <small id="emailHelp">We'll never share your email</small> </div> </fieldset> <fieldset> <legend>Pet Info</legend> <div> <label for="petname">Pet name</label> <input type="text" id="petname" placeholder="Your Pet's name"> </div> <div> <label for="pettype">Pet type</label> <select id="pettype"> <option>Choose</option> <option value="cat">Dog</option> <option value="cat">Cat</option> <option value="bird">Other</option> </select> </div> <div> <label for="reasonforvisit">Reason for today's visit</label> <textarea id="reasonforvisit" rows="3"></textarea> </div> <div> <label>Has your pet been spayed or neutered?</label> <label><input type="radio" name="spayneut" value="yes" checked> Yes</label> <label><input type="radio" name="spayneut" value="no"> No</label> </div> <div> <label>Has the pet had any of the following in the past 30 days</label> <label><input type="checkbox"> Abdominal pain</label> <label><input type="checkbox"> Lack of appetite</label> <label><input type="checkbox"> Weakness</label> </div> </fieldset> <button type="submit">Submit</button> </form> </div><!-- content container --> </body>
در ادامه شروع میکنیم به تزئین کردن این فرم، با کلاسهای بوت استرپ 4:
- ابتدا به fieldsetهای تعریف شده، کلاس form-goup را انتساب میدهیم. این مورد سبب میشود تا اندکی فاصله بین آنها ایجاد شود.
- سپس به تمام divهایی که المانها را در بر گرفتهاند نیز کلاس form-group را اعمال میکنیم.
با اینکار فاصلهی مناسبی بین المانهای تعریف شدهی در صفحه ایجاد میشود:
- در ادامه به تمام المانهای input، select و textarea (منهای checkboxها) کلاس form-control را نسبت میدهیم:
با اینکار، ظاهر این المانها بسیار شکیلتر شدهاست و همچنین این فرم واکنشگرا نیز میباشد.
- پس از آن، تمام المانهای label را انتخاب کرده و کلاس form-control-label را به آنها انتساب میدهیم. هرچند با اینکار ظاهر فعلی فرم تغییری نمیکند، اما چنین تعریفی برای فعالسازی کلاسهای اعتبارسنجی ضروری است.
اگر به هر دلیلی نخواستید این برچسبها را نمایش دهید، میتوانید از کلاس sr-only استفاده کنید که صرفا سبب نمایش آنها به screen readers میشود.
- ذیل فیلد ورود ایمیل، متنی وجود دارد. این متن را با کلاسهای form-text text-muted مزین میکنیم:
- به دکمهی پایین صفحه نیز کلاسهای btn btn-primary را اضافه میکنیم که در مطلب «بررسی شیوهنامههای المانهای پر کاربرد بوت استرپ 4» بیشتر به آنها پرداختیم.
مزین سازی checkboxها و radio-buttonها در بوت استرپ 4
روش مزین سازی checkboxها و radio-buttonها در بوت استرپ، با سایر المانها اندکی متفاوت است:
<div class="form-check"> <label class="form-check-label"> <input class="form-check-input" type="checkbox"> Lack of appetite </label> </div>
یک نکته: اگر نیاز است این المانها کنار یکدیگر نمایش داده شوند، میتوان بر روی div آنها از کلاسهای form-check form-check-inline استفاده کرد. در این حالت اگر میخواهید برچسب برای مثال radio-button تعریف شده، در یک سطر و گزینهها آن در سطری دیگر باشند، از کلاس d-block بر روی این برچسب استفاده کنید:
<div class="form-group"> <label class="d-block">Has your pet been spayed or neutered?</label> <div class="form-check form-check-inline"> <label class="form-check-label"> <input class="form-check-input" type="radio" name="spayneut" value="yes" checked> Yes </label> </div> <div class="form-check form-check-inline"> <label class="form-check-label"> <input class="form-check-input" type="radio" name="spayneut" value="no"> No </label> </div> </div>
کلاسهای کنترل اندازه و اعتبارسنجی المانهای فرمهای بوت استرپ 4
- با استفاده از کلاس form-control-sm میتوان اندازهی فیلدهای input را با ارتفاع کوچکتری نمایش داد و یا توسط کلاس form-control-lg میتوان آنها را بزرگتر کرد.
- کلاس form-inline سبب میشود تا یک form-group به صورت inline نمایش داده شود. یعنی برچسب و کنترلهای درون آن، در طی یک سطر نمایش داده خواهند شد. در این حالت، نیاز به کلاسهای Margin مانند mx-sm-2 خواهد بود تا فاصلهی بین کنترلها را بتوان کنترل کرد.
- برای نمایش نتایج اعتبارسنجی کنترلها:
- اگر کل فرم اعتبارسنجی شدهاست، کلاس was-validated را به المان form اضافه کنید.
- اگر اعتبارسنجی کنترلی با موفقیت روبرو شود، کلاس is-valid و اگر خیر کلاس is-invalid را به آن نسبت دهید.
- اگر میخواهید پیام خاصی را پس از موفقیت اعتبارسنجی نمایش دهید، آنرا درون یک div با کلاس valid-feedback قرار دهید و یا برعکس از کلاس invalid-feedback استفاده کنید.
- برای تغییر رنگ برچسب المانها نیز از کلاسهای text-color همانند قبل استفاده کنید؛ مانند text-success.
یک مثال:
<div class="form-group"> <label for="owneremail" class="text-success">Email address</label> <input class="form-control is-valid" type="email" id="owneremail" aria-describedby="emailHelp" placeholder="Enter email"> <small class="form-text text-muted" id="emailHelp">We'll never share your email</small> <div class="valid-feedback"> Looks good! </div> </div>
تغییر نحوهی چیدمان عناصر فرمها در بوت استرپ 4
فرم زیر را در نظر بگیرید:
قصد داریم با استفاده از کلاسهای ویژهی بوت استرپ 4، آنرا دو ستونی کنیم؛ به طوریکه برچسبها در یک ستون و فیلدهای ورودی، در ستونی دیگر نمایش داده شوند. همچنین این فرم واکنشگرا نیز باشد؛ به این معنا که این دو ستونی شدن، فقط در اندازههای پس از md رخ دهد:
<body> <div class="container"> <h2>Medical Questionnaire</h2> <form> <fieldset class="form-group"> <legend>Owner Info</legend> <div class="form-group row"> <label class="form-control-label col-md-2 col-form-label text-md-right" for="ownername">Owner</label> <div class="col-md-10"> <input class="form-control" type="text" id="ownername" placeholder="Your Name"> </div> </div> <div class="form-group row"> <label class="form-control-label col-md-2 col-form-label text-md-right" for="owneremail">Address</label> <div class="col-md-10"> <input class="form-control" type="text" id="owneremail" placeholder="Address"> </div> </div> <div class="form-group row"> <div class="form-group col-6 offset-md-2"> <label class="form-control-label sr-only" for="ownercity">City</label> <input class="form-control" type="text" id="ownercity" placeholder="City"> </div> <div class="form-group col-md-4 col-6"> <label class="form-control-label sr-only" for="ownerzip">Zip</label> <input class="form-control" type="text" id="ownerzip" placeholder="Zip"> </div> </div> <div class="form-group row"> <div class="offset-md-2 col-md-10"> <button class="btn btn-primary" type="submit">Submit</button> </div> </div> </fieldset> </form> </div> </body>
توضیحات:
برای ستونی کردن فرمها، ابتدا کلاس row، به form-group قرار گرفتهی داخل container اصلی اضافه میشود:
<div class="form-group row"> <label class="form-control-label col-md-2 col-form-label text-md-right" for="ownername">Owner</label> <div class="col-md-10"> <input class="form-control" type="text" id="ownername" placeholder="Your Name"> </div> </div>
پس از آن نوبت به تعریف ستون فیلد تعریف شدهاست که با ایجاد یک div و تعریف تعداد واحدی را که به خود اختصاص میدهد (col-md-10)، انجام میشود.
در اینجا برچسبهای فیلدهای city و zip با کلاس sr-only مشخص شدهاند. به همین جهت فقط به screen readers نمایش داده میشوند.
<div class="form-group row"> <div class="form-group col-6 offset-md-2"> <label class="form-control-label sr-only" for="ownercity">City</label> <input class="form-control" type="text" id="ownercity"placeholder="City"> </div>
<div class="form-group row"> <div class="offset-md-2 col-md-10"> <button class="btn btn-primary" type="submit">Submit</button> </div> </div>
ایجاد گروهی از ورودیها در بوت استرپ 4
برای افزودن آیکنهایی به فیلدهای ورودی، از روش ایجاد گروهی از ورودیها در بوت استرپ 4 استفاده میشود:
<div class="form-group"> <label class="form-control-label" for="donationamt"> Donation Amount </label> <div class="input-group"> <div class="input-group-prepend"> <span class="input-group-text">$</span> </div> <input type="text" class="form-control" id="donationamt" placeholder="Amount"> <div class="input-group-append"> <span class="input-group-text">.00</span> </div> </div> </div>
در بوت استرپ 4، کلاسهای input-group-addon و input-group-btn بوت استرپ 3 حذف و با کلاسهای input-group-prepend و input-group-append جایگزین شدهاند. از prepend برای قرار دادن آیکنی پیش از فیلد ورودی و از append همانند مثال فوق، برای قرار دادن آیکنی اختیاری پس از فیلد ورودی استفاده میشود.
نمونهی متداول دیگر آن، نحوهی تعریف ویژهی فیلد جستجوی سایت، در منوی راهبری آن است:
<nav class="navbar bg-dark navbar-dark navbar-expand-sm"> <div class="container"> <div class="navbar-brand d-none d-sm-inline-block"> Wisdom Pet Medicine </div> <div class="navbar-nav mr-auto"> <a class="nav-item nav-link active" href="#">Home</a> <a class="nav-item nav-link" href="#">Mission</a> <a class="nav-item nav-link" href="#">Services</a> <a class="nav-item nav-link" href="#">Staff</a> <a class="nav-item nav-link" href="#">Testimonials</a> </div> <form class="form-inline d-none d-md-inline-block"> <div class="input-group"> <label for="search" class="form-control-label sr-only"></label> <input type="text" id="search" class="form-control" placeholder="Search ..."> <div class="input-group-append"> <button class="btn btn-outline-light" type="submit">Go</button> </div> </div> </form> </div> </nav>
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_10.zip
در ،قسمت قبلی پیاده سازی درختها را بررسی کردیم و در این قسمت مبحث گرافها را آغاز میکنیم .
گرافها یکی از پر اهمیتترین و
همچنین پر استفادهترین ساختارها هستند و به خوبی روابط بین تمامی اشیاء را نشان میدهند
و در عمل تقریبا همه چیز را پشتیبانی میکنند. در ادامه متوجه خواهید شد که درختها،
زیر مجموعهای از گرافها هستند و همانطور که میدانید لیستها هم زیر مجموعهی درختها
هستند. پس لیستها هم زیر مجموعهی گرافها میشوند .
مفهوم پایهای گراف
در این بخش تعدادی از مهمترین خصوصیات
گرافها را بررسی میکنیم که تعدادی از آنها بسیار شبیه به ساختمان درختهاست، ولی
تفاوتهای زیادی با هم دارند؛ زیرا که درخت، خود نوع متفاوتی از ساختمان گرافها است .
درخت زیر را در نظر بگیرید؛ این درخت هم مانند سایر درختها با گرههای شماره دار، نامگذاری شده است که تشخیص آنها را برای
ما آسانتر میسازد. در گراف، به گرهها راس یا vertice هم
میگویند. هر چند نام گره هم برای آنها به کار برده میشود. به فلشهایی که به
این رئوس اشاره میکنند، لبههای جهت دار directed edges گفته میشود که در ویکی پدیا و کتب آموزشی فارسی، به آنها یال
اطلاق میشود . به یال هایی که از هر راس بیرون میآیند Predecessor گفته میشود که به معنی آغاز کننده است و به راسی که اشاره میکند، Successor گویند که
به معنی ارث برنده یا جایگزین شناخته میشود. در شکل زیر عدد راس 19 آغاز کننده راس
1 است و 1 هم جایگزین یا ارث برنده 19 و اگر با دقت به شکل نگاه کنید میبینید که
یک راس میتواند از چند راس ارث برنده باشد؛ مثل راس 21 .
برای نمایش گراف، ما از عبارت (V,E) استفاده میکنیم که
V مجموعهای از راسها و E مجموعهای از لبههاست. هر لبه (که با
e کوچک نمایش داده میشود) و در مجموعه E قرار دارد، پیوند دو راس
v و
u را نشان میدهد یا به عبارتی به صورت
ریاضی میشود (e=(u,v .
برای
اینکه مطالب را بهتر درک کنیم
بهتر است که هر راس را یک شهر و هر لبه را یک جادهی یک طرفه برای ارتباط با
این راسها فرض کنیم. مثلا اگر یکی از راسها را تهران تصور کنیم و دیگری را
کاشان، لبه یا
جادهی یک طرفهای که این دو شهر را به هم متصل میکند، میشود جاده یا لبهی تهران کاشان.
در بعضی مواقع از لبههای بدون جهت استفاده میشود که این لبهها را لبههای دو طرفه میگویند؛ مثل جادهی دو طرفه. گاهی هم از دو لبهی جهت دار به جای یک لبهی بدون جهت استفاده میکنند که نمونهی آن را در شکل زیر میبینید.
دو راسی که به وسیلهی یک یال به یکدیگر متصل میشوند را همسایه Neighbor مینامند. هر یال میتواند یک عدد برای خود داشته باشد که به این عدد وزن یال یا لبه میگویندWeight (Cost) و در مثال بالا میتوان گفت وزن هر یال میشود مسافت آن جاده؛ مسافتی که بین دو شهر همسایه باید طی شود. تصویر زیر یک گراف را نشان میدهد که وزن یالهای آن در کنار هر یال نوشته شده است.
مسیر Path در گراف همانند درختها، طی کردن مسیری است که از یک راس به راس دیگر میرسد. در مثال بالا باید گفت که برای رسیدن از شهر مبدا به شهر مقصد، باید از چه شهرهایی عبور کرد. در شکل بالا مسیر 1 - 12 - 19 - 21 - 7 - 21 و 1 مسیر نیستند؛ چرا که راس 21 هیچ لبهی آغاز کنندهای ندارد و بیشتر ارث برنده است.
طول Length هر مسیر هم تعداد یالهایی است که در طول مسیر قرار دارد یا تعداد راسها منهای یک؛ به این مثال دقت کنید:
مسیر 1 -12-19-21 مسیری است که طول آن سه میباشد.
وزن مسیر هم از جمع وزن یالهایی که در طول مسیر طی میشود به دست میآید.
حلقه Loop مسیری است که راس اولیه با راس نهایی یکی باشد. نمونهی آن مسیر 1-12-19-1 میباشد. ولی مسیر 1-7-21 حلقهای تشکیل نمیدهد.
لبهی حلقه ای Looping Edge لبهای است که مبداء یا آغاز کنندهی آن با مقصد یا ارث برندهی آن یکی باشد. یعنی به راسی وصل شود که از همان، آغاز شده است. مثل لبهی متصل به راس 14.
یک کلاس گراف به چه مواردی نیاز دارد:
عملیات ایجاد گراف
افزودن و حذف یک راس یا لبه
بررسی اینکه بین دو راس لبه ای وجود دارد یا خیر
جست و جوی جانشینهای یک راس
در قسمت آینده کد آن را در سی شارپ پیاده سازی خواهیم کرد.
با بالا رفتن تعداد اکشن متدهای یک پروژه، ممکن است استفاده از ValidateAntiForgeryToken فراموش شود. در ادامه مثالی را ملاحظه میکنید که یک پروژهی ASP.NET MVC را جهت یافتن اکشن متدهای Post ایی که فیلتر ValidateAntiForgeryToken ندارند، اسکن میکند:
using System; using System.Linq; using System.Reflection; // Add a ref. to \Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Web.Mvc.dll using System.Web.Mvc; // Add a ref. to System.Web using System.Web.UI; namespace FindOutputCaches { class Program { static void Main(string[] args) { var path = @"D:\path\bin\site.dll"; var asmTarget = Assembly.LoadFrom(path); checkCsrfTokens(asmTarget); Console.WriteLine("Press a key..."); Console.Read(); } private static void checkCsrfTokens(Assembly asmTarget) { // یافتن کلیه کنترلرها var controllers = asmTarget.GetTypes() .Where(type => typeof(IController).IsAssignableFrom(type) && !type.Name.StartsWith("T4MVC")) .ToList(); foreach (var controller in controllers) { // یافتن کلیه اکشن متدهای کنترلر جاری var actionMethods = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(method => typeof(ActionResult).IsAssignableFrom(method.ReturnType)) .ToList(); foreach (var method in actionMethods) { var httpPostAttributes = method.GetCustomAttributes(typeof(HttpPostAttribute), true); if (httpPostAttributes == null || !httpPostAttributes.Any()) continue; var csrfTokens = method.GetCustomAttributes(typeof(ValidateAntiForgeryTokenAttribute), true); if (csrfTokens == null || !csrfTokens.Any()) { Console.WriteLine("Detected [HttpPost] without [ValidateAntiForgeryToken] in:\n {0}-->{1}", controller.FullName, method.Name); } } } } } }
سپس در این اسمبلی، کلیه نوعهای تعریف شده، یافت گردیده و آنهایی که پیاده سازی کننده IController هستند (یعنی کلاسهای کنترلر واقعی برنامه)، جدا خواهند شد.
در ادامه در این کنترلرها، متدهایی را بررسی خواهیم کرد که دارای خروجی از نوع ActionResult باشند (فقط اکشن متدها مدنظر هستند). اگر این اکشن متد یافت شده دارای ویژگی HttpPost بود و همچنین ValidateAntiForgeryToken نداشت، یعنی یک مشکل امنیتی که باید برطرف شود.
برای رمز نگاری Connection String ابتدا Command Prompt را از مسیر زیر باز کنید
aspnet_regiis.exe -pef "connectionStrings" "sulotion path"
<system.net> <mailSettings> <smtp deliveryMethod="Network"> <network host="smtp.gmail.com" port="587" userName="username@gmail.com" password="password" /> </smtp> </mailSettings> </system.net>
aspnet_regiis.exe -pef "system.net/mailSettings/smtp" "sulotion path"
<system.net> <mailSettings> <smtp configProtectionProvider="RsaProtectedConfigurationProvider"> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <KeyName>Rsa Key</KeyName> </KeyInfo> <CipherData> <CipherValue>QuOFQrT6XwxDhQjFnM3EByleyWqYY6lA1cGK1Dzli/hrDOYSj35ADk4MB3PeLOMVYh76kB8vch0/iZKAZaJlNUPKi/iZjEzE755B3sILKGLxfkH3j3qKHB0x1WN65L6zBXgzufphCVaNRobQXOl5J3E0Df8VCf/bERZu741HLPs=</CipherValue> </CipherData> </EncryptedKey> </KeyInfo> <CipherData> <CipherValue>NdwBe0mWZ+Yg/DEzNuiDfXlGpicoH1ZMn54FTrLuVsY3rawS/k6KPID3bZvOWB/XYseTYFGhqs7FUEqIYMvWjJYYmDAzk6dd4iv9y6ch3ZcXWQ/R5TkQLWoLQPYgdwGI3uJNs22t28xUISm1wS0uDbizCM2Io+DzSQe8N4Ih2MP9mb2NCbZ4BZEBCPvCevpSpdEjGN9v7hk=</CipherValue> </CipherData> </EncryptedData> </smtp> </mailSettings> </system.net>
aspnet_regiis.exe -pdf "system.net/mailSettings/smtp" "sulotion path"