هدف | سرویسهای اضافه شده | متد تنظیم سرویسها |
- مناسب برای توسعهی Web API - اما باید بخاطر داشت که این سرویسها را به صورت پیشفرض اضافه نمیکند:
|
| ()AddControllers |
شبیه به ASP.NET Core 1.X عمل میکند؛ یعنی سرویس Pages را که مرتبط با Razor Pages است، به صورت پیشفرض ثبت نمیکند. |
| ()AddControllersWithViews |
برای کار با Razor pages بوده و این سرویسها را به صورت پیشفرض به همراه ندارد:
|
| ()AddRazorPages |
شبیه سازی customErrors در نگارشهای دیگر ASP.NET که در فایل web.config قابل تنظیم است:
<customErrors mode="On" defaultRedirect="error"> <error statusCode="404" redirect="error/notfound" /> <error statusCode="403" redirect="error/forbidden" /> </customErrors>
public void Configure(IApplicationBuilder app) { if (env.IsDevelopment()) { app.UseDatabaseErrorPage(); app.UseDeveloperExceptionPage(); } app.UseExceptionHandler("/error/index/500"); app.UseStatusCodePagesWithReExecute("/error/index/{0}");
public class ErrorController : Controller { private readonly ILogger<ErrorController> _logger; public ErrorController(ILogger<ErrorController> logger) { _logger = logger; } public IActionResult Index(int? id) { var logBuilder = new StringBuilder(); var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>(); logBuilder.AppendLine($"Error {id} for {Request.Method} {statusCodeReExecuteFeature?.OriginalPath ?? Request.Path.Value}{Request.QueryString.Value}\n"); var exceptionHandlerFeature = this.HttpContext.Features.Get<IExceptionHandlerFeature>(); if (exceptionHandlerFeature?.Error != null) { var exception = exceptionHandlerFeature.Error; logBuilder.AppendLine($"<h1>Exception: {exception.Message}</h1>{exception.StackTrace}"); } foreach (var header in Request.Headers) { var headerValues = string.Join(",", value: header.Value); logBuilder.AppendLine($"{header.Key}: {headerValues}"); } _logger.LogError(logBuilder.ToString()); if (id == null) { return View("Error"); } switch (id.Value) { case 401: case 403: return View("AccessDenied"); case 404: return View("NotFound"); default: return View("Error"); } } }
- و اگر UseStatusCodePagesWithReExecute فعال شده باشد، سرویس IStatusCodeReExecuteFeature اطلاعات مسیر اصلی درخواستی را ارائه میدهد.
- سپس بر اساس id ارسالی به این اکشن متد میتوان برای مثال صفحهی 404 (یافت نشد) و یا سایر صفحات دلخواه دیگری را به صورت انتخابی نمایش داد.
ما مطابق آموزشی که در این مقاله داده شده از یک اکشن متد برای ذخیره عکس ارسالی تو یک پوشه و سپس برگشت دادن مسیر عکس و از یک اکشن متد دیگه برای ذخیره اطلاعاتی که قراره همراه با فرم ارسال بشن (به همراه مسیر عکس برگشت داده شده)، استفاده میکنیم
مشکلی که ما موقع استفاده از این افزونه باهاش برخوردیم اینه که گاهی اوقات و همونطور که انتظار میره اکشن متد (AddAvatars) که وظیفه ذخیره عکس رو داره اول اجرا میشه و اکشن متد (Add) که وظیفه ذخیره اطلاعات رو داره دوم، ولی گاهی اوقات این ترتیب به هم میریزه و ابتدا اطلاعات ارسالی ذخیره میشه و بعد اکشن متد ذخیره عکس اجرا میشه.
سناریوی ما هم تا حدی شبیه به سناریویی هست که آقای احمدی مطرح کردند، ولی همونطور که گفتیم مشکل اصلی اینه که اکشن متدها هر بار با ترتیبهای متفاوت فراخوانی میشن
<div class="container-fluid"> @using (Ajax.BeginForm("Add", "Authors", new AjaxOptions { UpdateTargetId = "result", InsertionMode = InsertionMode.Replace, HttpMethod = "POST" }, new { @class = "form-horizontal", id = "UploadFile" })) { @Html.AntiForgeryToken() <div class="control-group"> <label class="control-label" for="AuhtorFirstNameAndLastName">نام نویسنده</label> <div class="controls"> @Html.TextBoxFor(author => author.AuhtorFirstNameAndLastName, new { placeholder = "نام نویسنده" }) </div> @Html.ValidationMessageFor(author => author.AuhtorFirstNameAndLastName) </div> <div class="control-group"> <label class="control-label" for="Status">ارسال عکس</label> <div class="controls"> <input type="file" name="avatarFile" id="avatarFile" /> </div> <div> @*<input type="submit" name="btn-submit" value="ارسال" class="btn btn-success" />*@ <img id="loading" alt="1" src="Images/loading83.gif" style="display: none;" /> </div> </div> <div id="result"></div> <input type="submit" name="btn-submit" value="افزودن نویسنده" class="btn btn-success" /> <input type="button" name="btn-colose" id="btn-close" value="انصراف" class="btn btn-danger" onclick="$dialog.dialog('close');" /> } </div> <script type="text/javascript"> $('#UploadFile').submit(function () { $("#loading").show(); $.ajaxFileUpload({ url: "@Url.Action("AddAvatar","Authors")", secureuri: false, fileElementId: 'avatarFile', dataType: 'json', data: {}, success: function (data, status) { $("#loading").hide(); }, error: function (data, status, e) { $("#loading").hide(); } }); }); </script>
EF Code First #11
1. لایه Data Access دارای یک Infrastructure است که فقط اینترفیسهای UnitOfWork و Repository در آن تعریف شده و هیچ خبری از IQueryable نیست مثلا برای دریافت داده صفحه بندی شده چیزی شبیه startRowIndex و maximumRows و حتی sortExpression پاس داده میشود.2. برای هر ORM یک پروژه جدا که به Data.Infrastructre رفرنس دارد ایجاد میشود. حالا در این پروژه UnitOfWork و Repository پیادهسازی میشود که از نظر داخلی به IQueryable وابسته است و مثلاً در پیادهسازی همان متد صفحه بندی شده به راحتی از LINQ استفاده میکند.3. هیچ کس حق ندارد در لایه های دیگر از IQueryable استفاه کند و باید ابتدا متد مورد نظرش را به Data.Infrastructure اضافه و بعد در لایه مرحله دو پیادهسازی کند.4. با استفاده از DI مسئله جایگذین کردن لایه مرحله 2 به راحتی اتفاق میافتد و هیچ لایهای مستقیم به پیادهسازی سمت Data وابسته نیست بلکه به Data.Infrastructure وابسته است.
من قبول دارم که این کار زمان بیشتری را میگیرد اما شما هم خوب میدانید که اگر میخواهیم وقت صرف کنیم و یک چهارچوب ماندگار ایجاد کنیم باید طراحی مستقل از تکنولوژی داشته باشیم (برای مثال حتی WPF و MVC که عرض کردم هم در چهارچوب جز Concrete class به حساب میآیند؛ هر چند از دید پروژه نهایی و مصرف کننده چهارچوب اینطور نیست).
مثال کاربردی این قضیه این است که در یک پروژه مجبور بودیم اطلاعات را رمزنگاری شده در DB ذخیره و بازیابی کنیم و البته فرم مورد نظر از هما CRUD های داخل چهارچوب بود و اگر این طراحی استفاه نمیشد مجبور بودیم برای آن یک فرم از یک ابتدا پیادهسازی کنیم و چون CRUD موجود در چهارچوب دارای امکانات قابل توجهی بود در زمان و هزینه تاثیر زیادی داشت.
[1] منظور از لایه، لایههای فیزیکی و سنتی نیست.
Constants
const PI: f32 = 3.14159;
constها را میتوان در هر محدودهای از جمله global scope تعریف کرد؛ اما مجاز به داشتن حالت تغییرپذیر، یا حاوی ارجاع نیستند. این مورد به این دلیل است که آنها تغییر ناپذیر هستند و نمیتوان آنها را در زمان اجرا تغییر داد. با این حال، آنها را میتوان در type annotations استفاده کرد:
const STR: &str = "hello, world!"; let mut s = String::from(STR);
Statics
static mut COUNT: i32 = 0; fn main() { unsafe { COUNT += 1; println!("count: {}", COUNT); } }
استاتیک را میتوان در هر محدودهای از جمله global scope تعریف کرد و میتوان از آن در type annotations درست مانند constها استفاده کرد. آنها همچنین میتوانند برای ذخیرهی منابعی که باید بین رشتهها به اشتراک گذاشته شوند، یا برای مقداردهی اولیهی global scope استفاده شوند.
ثابتها و استاتیکها در Rust دو نوع متغیر هستند که در طول اجرای برنامه مقادیر ثابتی دارند. ثابتها تغییر ناپذیر هستند و نمیتوانند ارجاعی داشته باشند، در حالیکه استاتیک میتواند تغییر پذیر باشد و حاوی ارجاع باشد. هر دو را میتوان در هر scope ای تعریف کرد و در type annotations استفاده کرد. درک تفاوت بین ثابتها و استاتیک برای نوشتن کد Rust ایمن و کارآمد، مهم است.
[DllImport( "nativelib", EntryPoint = "to_lower", CharSet = CharSet.Unicode)] internal static extern string ToLower(string str); // string lower = ToLower("StringToConvert");
تولید کنندهی کد مخصوص P/Invoke در دات نت 7، به دنبال ویژگی جدید LibraryImportAttribute بر روی متدهای استاتیک و partial میگردد تا کدهای متناظر با آنها را تولید کند. به این ترتیب نیاز به تولید اینگونه کدها در زمان اجرای برنامه مرتفع میشود و همچنین میتوان این کدها را در IDE خود بررسی و حتی دیباگ کرد.
[LibraryImport( "nativelib", EntryPoint = "to_lower", StringMarshalling = StringMarshalling.Utf16)] internal static partial string ToLower(string str);
امکان تبدیل خودکار کدهای قدیمی مبتنی بر DllImportAttribute به نمونههای جدید
برای تبدیل خودکار کدهای قدیمی موجود، فقط کافی است یک سطر زیر را به فایل editorconfig. پروژهی خود اضافه کنید:
dotnet_diagnostic.SYSLIB1054.severity = suggestion
تغییرات صورت گرفته نسبت به DllImport قدیمی
نحوهی تعریف LibraryImportAttribute در اکثر موارد با DllImportAttribute تطابق دارد، منهای موارد زیر:
- در اینجا معادلی برای CallingConvention وجود ندارد. برای اینکار از UnmanagedCallConvAttribute استفاده میشود.
- CharSet با StringMarshalling تعویض شدهاست. ANSI حذف شدهاست و UTF-8 حالت پیشفرض است. برای مثال:
// Before public static class Native { [DllImport(nameof(Native), CharSet = CharSet.Unicode)] public extern static string ToLower(string str); } // After public static partial class Native { [LibraryImport(nameof(Native), StringMarshalling = StringMarshalling.Utf16)] public static partial string ToLower(string str); }
Rust یک زبان برنامه نویسی سیستمی است که برای ارائهی عملکرد و کنترل یک زبان سطح پایین و در عین حال ارائه high-level abstractions و تضمین safety، طراحی شدهاست. این یک زبان منبع باز است که در ابتدا توسط موزیلا توسعه داده شد و اکنون توسط جامعهی بزرگی از توسعه دهندگان نگهداری میشود.
سینتکس Rust شبیه به ++C است. زبان برنامه نویسی Rust با ارائه memory safety, thread safety, and zero-cost abstractions باعث میشود کمتر مستعد خطاهای برنامه نویسی رایج باشد. فلسفهی این زبان، "Fearless Concurrency" است؛ به این معنا که طراحی شدهاست تا توسعه دهندگان را قادر به نوشتن کدهای همزمان، بدون ترس از ایجاد خطاهای مرتبط با حافظه کند.
چرا باید از Rust استفاده کنیم؟
دلیل اصلی محبوبیت Rust در بین توسعه دهندگان، ویژگیهای منحصر به فرد آن است؛ از جمله:
Memory Safety: ایمنی حافظه، ویژگی اصلی Rust است. Rust از سیستم ownership و borrowing برای اطمینان از تخصیص و آزادسازی صحیح حافظه استفاده میکند. سیستم ownership، مالکیت منابع را ردیابی میکند؛ در حالیکه سیستم borrowing دسترسی به منابع را برای جلوگیری از چندین مرجع تغییرپذیر، محدود میکند. این باعث میشود، کد Rust قابل اعتمادتر باشد و کمتر مستعد خطاهای مربوط به حافظه، مانند عدم ارجاع اشارهگر تهی و سرریز بافر باشد.
Thread Safety: مدیریت thread safety را از طریق ownership و borrowing انجام میدهد. سیستم ownership تضمین میکند که فقط یک رشته میتواند در یک زمان، مالک یک منبع باشد و از data races جلوگیری میکند. سیستم borrowing دسترسی به منابع را محدود میکند تا از چندین مرجع قابل تغییر جلوگیری کند که میتوانند باعث data races شوند.
Zero-Cost Abstractions: در بسیاری از زبانهای برنامهنویسی، استفاده از abstractions مانند higher-order functions ، closures یا generics میتواند هزینهی عملکردی داشته باشد. این مورد به این دلیل است که abstractions باید به کد ماشین ترجمه شود تا بتواند بر روی CPU اجرا شود. با این حال، سیستم abstractions بدون هزینهی Rust تضمین میکند که هیچ هزینهی عملکردی با استفاده از این انتزاعها وجود ندارد.
نتیجه گیری
Rust یک زبان برنامه نویسی برای سیستمهای مدرن است که memory safety, thread safety, and zero-cost abstractions را فراهم میکند. ویژگیها و مزایای منحصر به فرد Rust نسبت به سایر زبانهای برنامه نویسی، آن را به گزینهای عالی برای ساخت سیستمهای با کارآیی بالا، ایمن و همزمان تبدیل کردهاست. syntax، پشتیبانی از پلتفرمهای مختلف و جامعهی رو به رشد Rust، آن را به زبانی ایدهآل، برای توسعه دهندگانی که میخواهند نرم افزاری قوی و قابل اعتماد بسازند، تبدیل کردهاست.
همه ما با DisplayAttribute در DataAnnotaion آشنا هستیم. چیزی شبیه زیر برای یک موجودیت:
public class Student{ [Display(Name="نام خانوادگی")] public string FamilyName { get; set;} }
با استفاده از tag helper ای به نام asp-for میتوان متادیتای Name را به کاربر، در سمت رابط کاربری نشان داد؛ برای مثال:
<label asp-for="FamilyName"></label>
و یا موقع اعتبارسنجی میتوان به جای نشان دادن نام FamilyName از نام مفهومتری مانند نام خانوادگی استفاده نمود.
چه خوب بود اگر میشد علاوه بر نام، توصیفی از فیلد نیز برای آن در این قسمت وجود داشته باشد؛ به عبارت دیگر اگر کد زیر را داشتیم:
[Display( Name = "نام خانوادگی", Description = "بهتر است فقط در اینجا نام خانوادگی شخص وارد شود")] public string FamilyName{ get; set; }
بتوان از tag helper ای مانند زیر استفاده نمود:
<span asp-description-for="FamilyName"></span>
که در نهایت چنین خروجی html ای داشته باشیم:
<span>بهتر است فقط در اینجا نام خانوادگی شخص وارد شود</span>
برای این منظور میتوان از کلاس زیر بهره برد:
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; [HtmlTargetElement("div", Attributes = ForAttributeName)] [HtmlTargetElement("p", Attributes = ForAttributeName)] [HtmlTargetElement("span", Attributes = ForAttributeName)] public sealed class DescriptionForTagHelper : TagHelper { private const string ForAttributeName = "asp-description-for"; [HtmlAttributeName(ForAttributeName)] public ModelExpression For { get; set; } = default!; public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } var description = For.Metadata.Description; if (description != null) { // Do not update the content if another tag helper // targeting this element has already done so. if (!output.IsContentModified) { var childContent = await output.GetChildContentAsync(); if (childContent.IsEmptyOrWhiteSpace) { output.Content.SetHtmlContent(description); } else { output.Content.SetHtmlContent(childContent); } } } } }
کلاس DescriptionForTagHelper از کلاس پایه TagHelper ارث بری نموده است و متد ProcessAsync آن به نحوی که asp-description-for را بپذیرد override شده است.
حوزه اعمال این tag helper به span، p و div محدود شده است؛ اما میتوان با گذاشتن یک ستاره (*) آن را به کل المانهای html اعمال کرد.
اینطور که در این مطلب عنوان شده، ماوسهای قدیمی در اثر مشکلات سخت افزاری، میتوانند بهازای هر کلیک کاربر، دو سیگنال کلیک، ظرف مدت کوتاهی (برای مثال 5 میلی ثانیه) تولید کنند. برنامههای مبتنی بر Blazor، توسط متدهای نامتقارن میتوانند هردوی این سیگنالها را دریافت کرده و بنابراین متد مربوطه در کسری از ثانیه دوبار اجرا خواهد شد.
برای رهایی از این مشکل میتوان از کدی شبیه زیر بهره جست:
<button disabled="@_busy" Value="do-stuff" /> code{ private bool _busy = false; public async Task Handler() { if(_busy) return; _busy = true; try { // do your thing } finally { _busy = false; } } }
منطق آن ساده است؛ تا زمانی که اجرای متد، پایان نپذیرفتهاست، دکمهی مربوطه غیرفعال میگردد، تا نتوان دوباره روی آن کلیک کرد.
اگر نمیخواهید به ازای هر کامپوننت، این کدهای تکراری را ایجاد کنید، میتوانید کدهای فوق را در قالب یک کامپوننت مانند زیر ایجاد کنید (با نام دلخواه HandleValidSubmitForm.razor):
<EditForm Model="Model" OnValidSubmit="HandleValidSubmit"> @ChildContent?.Invoke(context) <button disabled="@_busy">Submit</button> </EditForm> @code { private bool _busy; [Parameter] public object? Model { get; set; } [Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; } [Parameter] public RenderFragment<EditContext>? ChildContent { get; set; } private async Task HandleValidSubmit(EditContext editContext) { if (_busy) return; _busy = true; try { await OnValidSubmit.InvokeAsync(editContext); } finally { _busy = false; } } }
سپس میتوانید در دیگر کامپوننتها به شکل زیر از آن بهره ببرید.
<HandleValidSubmitForm Model="_customer" OnValidSubmit="HandleValidSubmit"> <InputText @bind-Value="_customer.FirstName" /> <InputText @bind-Value="_customer.LastName" /> </HandleValidSubmitForm> @code { private Customer _customer = new Customer(); private async Task HandleValidSubmit() { // do your thing } public class Customer { public string? FirstName { get; set; } public string? LastName { get; set; } } }
Blog ID BlogID int Title nvarchar(250) Tags nvarchar(500)
Blog Tags Tbl BlogID int TagID int
در مورد کد دوم هم شاید بتوان به انباشته شدن زیاد سطرها و یا عدم ساختار مدرن اشاره کرد.
CREATE TABLE sal_emp ( name text, pay_by_quarter integer[], schedule text[][] );
راجع به نوع داده array در postgres در بیشتر مطالعه کنید.
سایر مزیتهای postgres را میتوانید از زبان shayro jan sky، در کانال دات نت و در ویدیو لینک شده مشاهده کنید.
مزیت بزرگ آن که باعث میشود تا از آن بتوانیم در پروژههای خود استفاده کنیم، سازگاری آن با ef core میباشد. یعنی اگر کل برنامهی شما با ef core پیاده سازی شده باشد، با عوض کردن متد UseSqlServer به UseNpgsql در کلاس program، مشکلی در برنامه رخ نخواهد داد و اپلیکیشن شما بجای استفاده از sql server به راحتی از postgres استفاده خواهد کرد.
متد نام برده شده در پکیج زیر قابل دسترسی میباشد:
Npgsql.EntityFrameworkCore.PostgreSQL
کارها تماما مانند ef و حتی با کتابخانههای مربوط به آن انجام خواهد شد و تنها تغییر در کدها، همین متد UseNpgsql میباشد که provider را عوض خواهد کرد.
در قسمت بعد به نصب و راه اندازی postrgress و دشبردهای مدیریتی آن از طریق داکر و پیاده سازی CRUD خواهیم پرداخت.