1) بهروز رسانی شماره نگارش داتنت
اولین قدم در جهت ارتقاء پروژههای قدیمی، تغییر شماره نگارش TargetFramework موجود در فایل csproj. به net8.0 است. پس از اینکار نیاز است تمام بستههای نیوگت موجود را نیز به نگارشهای جدیدتر آنها ارتقاء دهید.
2) فعالسازی حالت SSR تعاملی سمت سرور
پایهی تمام تغییرات انجام شدهی در Blazor 8x، قابلیت SSR است و تمام امکانات دیگر برفراز آن اجرا میشوند. به همین جهت پس از ارتقاء شماره نگارش داتنت، نیاز است SSR را فعال کنیم و برای اینکار باید به هاست ASP.NET Core بگوئیم که درخواستهای رسیده را به کامپوننتهای Razor هدایت کند. بنابراین، به فایل Program.cs مراجعه کرده و دو تغییر زیر را به آن اعمال کنید:
// ... builder.Services.AddRazorComponents().AddInteractiveServerComponents(); // ... app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
در اینجا ترکیب کامپوننتهای تعاملی سمت سرور (AddInteractiveServerComponents) و رندر تعاملی سمت سرور (AddInteractiveServerRenderMode)، دقیقا همان Blazor Server قدیمی است که ما با آن آشنا هستیم.
یک نکته: اگر از قالب جدید dotnet new blazor --interactivity None استفاده کنیم، یعنی حالت تعاملی بودن آنرا به None تنظیم کنیم، کلیات ساختار پروژهای را که مشاهده خواهیم کرد، با حالت تعاملی Server آن یکی است؛ فقط در تنظیمات Program.cs آن، گزینههای فوق را نداریم و به صورت زیر ساده شدهاست:
// ... builder.Services.AddRazorComponents(); // ... app.MapRazorComponents<App>();
3) ایجاد فایل جدید App.razor
در دات نت 8، دیگر خبری از فایل آغازین Host.cshtml_ پروژههای Blazor Server قدیمی نیست و کدهای آن با تغییراتی، به فایل جدید App.razor منتقل شدهاند. در این قسمت، کار هدایت درخواستهای رسیده به کامپوننتهای برنامه رخ میدهد و از این پس، صفحهی ریشهی برنامه خواهد بود.
در این تصویر، مقایسهای را بین جریان پردازش یک درخواست رسیده در دات نت 8، با نگارش قبلی Blazor Server مشاهده میکنید. در دات نت 8، فایل Host.cshtml_ (یک Razor Page آغازین برنامه) با یک کامپوننت Razor به نام App.razor جایگزین شدهاست و فایل قدیمی App.razor این پروژهها به Routes.razor، تغییر نام یافتهاست.
نمونهای از فایل App.razor جدید را که در قسمت قبل نیز معرفی کردیم، در اینجا با جزئیات بیشتری بررسی میکنیم:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="MyApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
- تمام دایرکتیوهای تعریف شده مانند page ،@addTagHelper@ و غیره حذف شدهاند.
- base href تعریف شده اینبار فقط با یک / شروع میشود و نه با /~. این مورد خیلی مهم است! اگر به آن دقت نکنید، هیچکدام از فایلهای استاتیک برنامه مانند فایلهای css. و js.، بارگذاری نخواهند شد!
- پیشتر برای رندر HeadOutlet، از یک تگهلپر استفاده میشد. این مورد در نگارش جدید با یک کامپوننت ساده جایگزین شدهاست.
- تمام component tag helperهای پیشین حذف شدهاند و نیازی به آنها نیست.
- ارجاع پیشین فایل blazor.server.js با فایل جدید blazor.web.js جایگزین شدهاست.
یک نکته: همانطور که مشاهده میکنید، فایل App.razor یک کامپوننت است و اینبار به همراه تگ <script> نیز شدهاست. یعنی در این نگارش از Blazor میتوان اسکریپتها را در کامپوننتها نیز ذکر کرد؛ فقط با یک شرط! این کامپوننت حتما باید SSR باشد. اگر این تگ اسکریپتی را در یک کامپوننت تعاملی ذکر کنید، همانند قابل (و نگارشهای پیشین Blazor) با خطا مواجه خواهید شد.
4) ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده
همانطور که عنوان شد، فایل قدیمی App.razor این پروژهها به Routes.razor تغییر نام یافتهاست که درج آنرا در قسمت body مشاهده میکنید. محتوای این فایل نیز به صورت زیر است:
<Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> </Router>
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
@page "/StatusCode/{responseCode}" <h3>StatusCode @ResponseCode</h3> @code { [Parameter] public string? ResponseCode { get; set; } }
یک نکته: اگر پروژهای را بر اساس قالب dotnet new blazor --interactivity Server ایجاد کنیم، در فایل Program.cs آن، چنین تنظیمی اضافه شدهاست:
if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); }
سؤال: در اینجا (برنامههای Blazor Server) چه تفاوتی بین UseExceptionHandler و UseStatusCodePagesWithRedirects وجود دارد؟
میانافزار UseExceptionHandler برای مدیریت استثناءهای آغازین برنامه، پیش از تشکیل اتصال دائم SignalR وارد عمل میشود. پس از آن و تشکیل اتصال وبسوکت مورد نیاز، فقط از میانافزار UseStatusCodePagesWithRedirects استفاده میکند.
اگر علاقمند نیستید تا تمام خطاهای رسیده را همانند مثال فوق در یک صفحه مدیریت کنید، میتوانید حداقل سه فایل زیر را به برنامه اضافه کنید تا خطاهای متداول یافت نشدن آدرسی، بروز خطایی و یا عدم دسترسی را مدیریت کنند:
404.razor
@page "/StatusCode/404" <PageTitle>Not found</PageTitle> <h1>Not found</h1> <p role="alert">Sorry, there's nothing at this address.</p>
500.razor
@page "/StatusCode/500" <PageTitle>Unexpected error</PageTitle> <h1>Unexpected error</h1> <p role="alert">There was an unexpected error.</p>
401.razor
@page "/StatusCode/401" <PageTitle>Not Authorized</PageTitle> <h1>Not Authorized</h1> <p role="alert">Sorry, you are not authorized to access this page.</p>
5) تعاملی کردن سراسری برنامه
پس از این تغییرات اگر برنامه را اجرا کنید، بر اساس روش جدید static server-side rendering کار میکند و تعاملی نیست. یعنی تمام کامپوننتهای آن به صورت پیشفرض، یکبار بر روی سرور رندر شده و خروجی آنها به مرورگر کاربر ارسال میشوند و هیچ اتصال دائم SignalR ای برقرار نخواهد شد. برای فعالسازی سراسری قابلیتهای تعاملی برنامه و بازگشت به حالت Blazor Server قبلی، به فایل App.razor مراجعه کرده و دو تغییر زیر را اعمال کنید تا به صورت خودکار به تمام زیرکامپوننتها، یعنی کل برنامه، اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" /> ... <Routes @rendermode="@InteractiveServer" />
نکته 1: اجرای دستور زیر در داتنت 8، قالب پروژهای را ایجاد میکند که رفتار آن همانند پروژههای Blazor Server نگارشهای قبلی داتنت است (این مورد را در قسمت قبل بررسی کردیم)؛ یعنی همهجای آن به صورت پیشفرض، تعاملی است:
dotnet new blazor --interactivity Server --all-interactive
نکته 2: البته ... InteractiveServer، دقیقا همان حالت پیشفرض برنامههای Blazor Server قبلی نیست! این حالت رندر، به صورت پیشفرض به همراه پیشرندر (pre-rendering) هم هست. یعنی در این حالت، روال رویدادگردان OnInitializedAsync یک کامپوننت، دوبار فراخوانی میشود (که باید به آن دقت داشت و عدم توجه به آن میتواند سبب انجام دوبارهی کارهای سنگین آغازین یک کامپوننت شود)؛ یکبار برای پیشرندر صفحه به صورت یک HTML استاتیک (بدون فعال سازی هیچ قابلیت تعاملی) که برای موتورهای جستجو و بهبود SEO مفید است و بار دیگر برای فعالسازی قسمتهای تعاملی آن، درست پس از زمانیکه اتصال SignalR صفحه، برقرار شد (البته امکان فعالسازی حالت پیشرندر در Blazor Server قبلی هم وجود داشت؛ ولی مانند Blazor 8x، به صورت پیشفرض فعال نبود). در صورت نیاز، برای سفارشی سازی و لغو آن میتوان به صورت زیر عمل کرد:
@rendermode InteractiveServerRenderModeWithoutPrerendering @code{ static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = new InteractiveServerRenderMode(false); }
در مورد پیشرندر و روش مدیریت دوبار فراخوانی شدن روال رویدادگردان OnInitializedAsync یک کامپوننت در این حالت، در قسمتهای بعدی این سری بیشتر بحث خواهد شد.
در کنار کاربرگ contents کاربرگی با نام Propertiesوجود دارد که میتوانید یک سری تنظیمات را برای plan خود انجام دهید. این تنظیمات از قبیل تغییر عنوان plan، تعیین مسیر پروژه، تاریخ شروع و پایان، کاربری که مالک این plan است، وضعیت جاری تستهای plan و تعیین مرورگر و ویندوز نیز میباشد که میتوانید در تصویر زیر آن را مشاهده کنید.
اگر در لیست کشویی مربوط به test settings مقدار <default> قرار داشت میتوانید با انتخاب آیتم new از لیست settings جدیدی را ایجاد نمایید و یا میتوانید لیست test settings هایی را که قبلا ایجاد کرده اید انتخاب نمایید و برای ویرایش آن با کلیک بر روی لینک open که کنار لیست قرار دارد، میتوانید تنظیمات را ویرایش نمایید.
همانطور که در تصویر بالا مشاهده میکنید، در سمت چپ، بخش هایی برای انجام تنظیمات مربوط به تست وجود دارد. در قسمت general تنظیماتی از قبیل عنوان test settings، شرح و نوع اجرای دستی یا اتومات بودن تستتان وجود دارد. در بخش roles میتوانید نقش هایی را برای این تست انتخاب نمایید و در قسمت data and diagnostics میتوانید یک سری اطلاعاتی را که میخواهید در زمان تست دریافت کنید، انتخاب کنید. برای اطلاعات بیشتر در مورد این بخش میتوانید در سایت مایکروسافت مطالعه کنید.
حالا بر میگردیم به بخش contents و موارد تست خود را میسازیم. همانطور که در تصویر پایین مشاهده میکنید در بخش contents و در سمت راست پنجره یک گزینه ای به نام configuration وجود دارد.
در configuration شما میتوانید یک سری تنظیمات مربوط به test شما است انجام دهید مثلا نوع مرورگری که میخواهید تست خود را اجرا کنید و یا اولویت تست را مشخص نمایید یا حتی نوع سیستم عامل را مشخص کنید. هم چنین میتوانید چندین configuration تعریف کنید و از هر کدام برای یک test suite استفاده کنید. به صورت پیش فرض test suite از تنظیمات config والد خودش یعنی test plan استفاده میکند.
دوباره برمی گردیم به بخش contents و میخواهیم یک test suite با استفاده از add requirements بسازیم. همانطور که در بخشهای قبل توضیح دادم میتوانیم به چند روش test suite بسازیم که یکی از آنها همین add requirements بود که میتوانستید از test suite هایی که قبلا ساخته اید به این پروژه تستتان اضافه کنید.
با انتخاب گزینه add requirements پنجره ای باز میشود که میتوانید همه test suiteها را مشاهده کنید و حتی میتوانید براساس عنوان و یا وضعیت تست و ... فیلتر کنید.
بعد از اینکه در قسمت بالا کوئری خود را تنظیم کردید با انتخاب گزینه run میتوانید کوئری خود را اجرا کرده و لیست test suiteها را براساس آن کوئری فیلتر کنید. میتوانید یک یا چند سطر را انتخاب کرده و با زدن دکمه add requirements to plan آنها را به plan خود اضافه نمایید. حالا ما یک test suite با استفاده از test suite هایی که قبلا ساخته ایم ایجاد کردیم. حالا باید مورد تستهای مان را به این test suite اضافه کنیم. در سمت راست با کلیک بر روی گزینه add پنجره ای مشابه پنجره بالا باز میشود که شما میتوانید test caseها را فیلتر کنید و یک یا چند مورد را انتخاب کرده و با زدن دکمه add test cases آنها را به test suite تان اضافه کنید. برای اضافه کردن مورد تست جدید هم میتوانید با کلیک بر روی new که در کنار گزینه Add قرار دارد مورد تست جدیدی را بسازید.
در تصویر زیر میتوانید بخشهای مختلف تست را که در بخشهای قبل هم توضیح دادم ببینید.
بنابراین برای ایجاد یک کامپوننت میتوانیم به اینصورت عمل کنیم:
var app = angular.module("dntModule", []); app.component("pmApp", { template: `Hello this is a simple component` });
همانطور که مشاهده میکنید تابع component دو پارامتر را از ورودی دریافت خواهد کرد؛ نام کامپوننت و یک شیء برای تعیین تنظیمات کامپوننت. نام کامپوننت در اینجا به صورت camel case تعریف شده است؛ که در واقع یک convention برای Angular است. در اینحالت برای استفادهی از کامپوننت باید به اینصورت عمل کنیم:
<pm-app></pm-app>
در قسمت تنظیمات کامپوننت، در سادهترین حالت یک template تعیین شدهاست که بیانگر نحوهی رندر شدن یک کامپوننت میباشد. در اینحالت وقتی انگیولار به تگ فوق برسد، یک کامپوننت با نام pmApp را بارگذاری خواهد کرد.
ایجاد یک کامپوننت ساده
در ادامه میخواهیم یک کامپوننت ساده را جهت نمایش یکسری URL درون صفحه طراحی کنیم. ساختار صفحه index.html به صورت زیر خواهد بود:
<html ng-app="DNT"> <head> <meta charset="UTF-8"> <title>Using Angular Component</title> <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"> <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css"> </head> <body> <div class="container"> <div class="row"> <div class="col-md-3"> <dnt-widget></dnt-widget> </div> </div> </div> <script src="bower_components/angular/angular.js"></script> <script src="scripts/app.js"></script> <script src="scripts/components/dnt-widget.component.js"></script> </body> </html>
در اینجا ابتدا توسط دایرکتیو ng-app، به Angular، ماژولمان را معرفی کردهایم. سپس مداخل بوتاسترپ و کتابخانهی font-awesome را مشاهده میکنید. در ادامه، کتابخانهی Angular و همچنین فایل app.js جهت معرفی ماژول برنامه معرفی شدهاست. در نهایت نیز یک فایل در مسیر ذکر شده برای قرار دادن کدهای کامپوننت در مسیر scripts/components اضافه شدهاست.
همانطور که ملاحظه میکنید، کامپوننتمان به صورت یک تگ سفارشی، درون صفحه قرار گرفته است:
<dnt-archive></dnt-archive>
در ادامه باید به Angular، نحوهی تعریف این کامپوننت را اعلام کنیم. بنابراین یک فایل جاوا اسکریپتی را با نام dnt-widget.component، با محتویات زیر ایجاد کنید:
(function () { "use strict"; var app = angular.module("DNT"); function DntArchiveController() { var model = this; model.panel = { title: "Panel Title", items: [ { title: "Dotnettips", url: "https://www.dntips.ir" }, { title: "Google", url: "http://www.google.con" }, { title: "Yahoo", url: "http://www.yahoo.con" } ] }; }; app.component("dntWidget", { templateUrl: '/scripts/components/dnt-widget.component.html', controllerAs: "model", controller: DntArchiveController }); } ());
توضیح کدهای فوق:
همانطور که مشاهده میکنید، برای پارمتر دوم کامپوننت، سه پراپرتی را تعیین کردهایم:
templateUrl: به کمک این پراپرتی به Angular گفتهایم که محتوای قالب این کامپوننت، درون یک فایل HTML مجزا قرار دارد و به صورت linked template میباشد.
controllerAs: یکی از مزایای استفاده از کامپوننتها، استفاده از controller as syntax میباشد. لازم به ذکر است اگر این پراپرتی را مقداردهی نکنیم، به صورت پیشفرض مقدار ctrl$ در نظر گرفته خواهد شد.
controller: مزیت دیگر کامپوننتها، استفاده از کنترلرها است. با استفاده از این پراپرتی، یک کنترلر را برای کامپوننتمان رجیستر کردهایم. در نتیجه زمانیکهی Angular میخواهد کامپوننتمان را نمایش دهد، تابع تعریف شده برای این پراپرتی، جهت ایجاد یک controller instance فراخوانی خواهد شد. بنابراین هر پراپرتی یا تابعی که برای این controller instance تعریف کنیم، به راحتی درون ویوی آن جهت اعمال بایندینگ در دسترس خواهد بود (در نتیجه نیازی به scope$ نخواهد بود).
درون کنترلر نیز برای راحتی کار و همچنین به عنوان یک best practice، مقدار this را توسط یک متغیر با نام model، کپچر کردهایم. در اینجا یک شیء را با نام panel نیز به مدل اضافه کردهایم.
محتویات تمپلیت:
<div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"> <span class="fa fa-archive"></span> {{ model.panel.title}} </h3> </div> <ul class="list-group"> <li class="list-group-item" ng-repeat="item in model.panel.items"> <span class="fa fa-industry"></span> <a href="{{ item.url }}">{{ item.title }}</a> </li> </ul> </div>
ویوی کامپوننت پیچیدگی خاصی ندارد. همانطور که مشاهده میکنید یک پنل بوتاسترپی را ایجاد کردهایم که مقدار عنوان آن و همچنین آیتمهای آن، از شیء اتچ شده به مدل دریافت خواهند شد. بنابراین اکنون اگر برنامه را اجرا کنید، خروجی کامپوننت را به اینصورت مشاهده خواهید کرد:
همانطور که مشاهده میکنید استفاده از کامپوننتها در Angular 1.5 در مقایسه با ایجاد دایرکتیوها و کنترلرها خیلی سادهتر است. در واقع امکانات این API جدید تنها به مثال فوق ختم نمیشود؛ بلکه این API یک سیستم مسیریابی جدید را نیز معرفی کرده است که در قسمتهای بعدی به آن نیز خواهیم پرداخت.
جهت تکمیل بحث نیز یک تقویم شمسی ساده را در اینجا قرار دادهام. میتوانید جهت مرور بحث جاری به کدهای آن مراجعه کنید. البته هدف از تعریف این پروژه تنها یک مثال ساده برای معرفی کامپوننتها بود و طبیعتاً باگهای زیادی دارد. اگر مایل بودید میتوانید در توسعهی آن مشارکت نمائید.
کدهای این قسمت را نیز از اینجا میتوانید دریافت کنید.
جلسه اول:
اولین قدم در تولید و توسعه نرم افزار داشتن یک نگرش سیستمی به بسته یا محصول نرم افزاری میباشد. اما چرا ما باید نرم افزار را به عنوان یک سیستم در نظربگیریم ؟
جواب این سئوال را باید از تعریف تئوری سیستم و خصوصیاتی که یک سیستم دارا میباشد استخراج کنیم.
تئوری سیستمها
دانشی برای سهولت کار با سیستمها و بررسی دقیق این مفهوم است ؛ در واقع تئوری سیستمها روشی برای شناخت محیط اطراف یا روشی برای شناخت دنیای واقع میباشد .
از تعریف فوق میتوان نتیجه گرفت :
برنامه نویسان برای ساخت برنامه هایی که با نیاز کاربران همسو باشد ، نیاز به شناخت محیطی دارند که کاربران در آن فعالیت میکنند پس برای شناخت محیط باید با دید سیستمی به مسئله نگاه کرد.
خصوصیات مهم سیستم :
1. محیط – Environment: هر سیستم در یک محیط قرار دارد.
2. مرز – Boundary : سیستمهای موجود در یک محیط توسط مرزها از یکدیگر جدا میشوند.
3. ورودی و خروجی – I/O : هر سیستم ورودی هایی را از محیط میگیرد و خروجی هایی را به محیط پس میدهد.
4. واسط – Interface : امکان محاوره سیستمها در یک محیط را فراهم میکند.
5. زیر سیستم – Sub System : هر سیستم میتواند حاوی چندین زیرسیستم باشد . زیر سیستمها تمام خصوصیتهای یک سیستم را دارا میباشند.
6. مکانیزم کنترلی – Controller : مهمترین بخش یک سیستم میباشد. مکانیزم کنترلی در واقع کنترل کننده تمامی فعالیتهای انجام شده توسط یک سیستم است . ورودیها از طریق مکانیزم کنترلی دریافت میشود و بر اساس آن خروجی هایی به محیط پس داده میشود.
نتیجه گیری :
با توجه به خصوصیاتی که در مورد سیستمها مطرح شد به راحتی میتوانیم دلیل علاقه مندی برنامه -نویسان به نوع نگرش سیستمی را در یابیم ، و جود محیط پیرامون یک سیستم و نحوه تبادل اطلاعات این سیستم با سایر سیستمها در این محیط ، شکستن یک سیستم به چند زیر سیستم برای راحتی مسئله و پیاده سازی آسانتر آن و نیز وجود اینترفیسها برای برقراری محاوره ای استاندارد بین زیر سیستمهای یک سیستم و همچنین وجود ورودی هاو تصمیم گیری براساس ورودی هاو تولید یک خروجی همه و همه از نکات مورد توجه برنامه نویسان در تولید یک بسته نرم افزاری هستند که هماهنگی کاملی با مفاهیم تئوری سیستمها دارند.
const ProtectedRoute = ( {children,roles }) => { const isLoggedIn=authService.isLoggedIn(); if (!isLoggedIn) { return <Navigate to="/login" replace />; } if(roles) { //checkRoles if(result_roles===false) return <Navigate to="/login" replace />; } return children; }; export default ProtectedRoute
<Routes> <Route path="/product/new" element={ <ProtectedRoute roles={["hesabdar", "anbardar"]}> <AdminTemplate> <NewProduct/> </AdminTemplate> </ProtectedRoute> }/> </Routes>
استفاده از CSS علاوه بر جذابیت و قابلیتهای مفید آن، پیچیدگی هایی دارد و کدهای شما معمولا طولانی میشود و هرچه کدها طولانیتر شوند، مدیریت آن نیز سختتر میگردد. اما با استفاده از SASS ، قابلیت هایی به Css اضافه میشود که قبلا وجود نداشت، از جمله استفاده از varible ها، نوشتن کدهای تو در تو ( nesting ) و … . با استفاده از SASS کدهای CSS کوتاهتر شده و در نتیجه سریعتر اجرا شوند. SASS با CSS3 سازگار است. همچنین امکان مشاهده فایلهای آن (با پسوند .scss ) توسط افزونه Firesass For Firebug وجود دارد.
دو syntax برای SASS وجود دارد: یکی SCSS (Sassy CSS) که شکل توسعه یافته CSS3 می باشدو دیگری که قدیمیتر است، Indented syntax میباشد که در آن به جای استفاده از براکت، از تورفتگی خطهای کد استفاده میشود و همچنین از به جای استفاده از سمی کولن ، باید به خط جدید بروید.
قابلیتهای موجود در SASS :
1- Variables
متغیرها امکان ایجاد تغییرات در کدهای CSS را بسیار راحتتر میسازند. به عنوان مثال یک متغیر برای یک کد رنگ دلخواه تعریف میکنید، از این به بعد به جای استفاده از کد رنگ در کدهای CSS ، از متغیر تعریف شده برای آن بهره میگیرید، به این ترتیب ، چنانچه در آینده نیاز به تغییر این کد رنگ داشته باشید، تنها با تغییر آن در متغیر ، در کل فایل CSS تغییر ایجاد خواهد شد . برای تعریف متغیر ، در ابتدای اسم دلخواه خود از علامت $ استفاده کنید:
$myColor: #ff0000; body { color: $myColor; } .box{ Border-color:$myColor; }
Nesting -2 یا selector های تو در تو:
می توانید selector ها را مانند کدهای html به صورت hirearchy تعریف کنید:
nav { ul { list-style: none; } li { display: inline-block; } a { text-decoration: none; } }
nav ul { list-style: none; } nav li { display: inline-block; } nav a { text-decoration: none; }
3- Partials :
می توانید قطعاتی از کدهای CSS را به صورت Partial SASS تعریف کنید و سپس آن را در فایلهای SASS دیگر استفاده نمایید.همانند Partialview در MVC ، هنگام نام گذاری آن از _ در ابتدای نام استفاده نمایید. فایل partial SASS دارای پسوند .SCSS می باشد : " "_myPartial.scss
برای استفاده از _myPartial.scss در فایل sass دیگر ، از دایرکتیو @import استفاده کنید:
@import "myPartial"
@import "mypartial1","myPartial2"
/*_myPartial1.scss codes…*/ html,body,ul,ol { margin: 0; padding: 0; } /*_myPartial2.scss codes…*/ @import "myPartial1" body, { background-color: #efefef; }
html, body, ul, ol { margin: 0; padding: 0; } body { background-color: #efefef; }
4- Mixins :
از آنجایی که استفاده و نوشتن بعضی property های CSS سخت میباشد، میتوانید از روش mixin استفاده کرده و قطعه کدهایی را ایجاد کنید که بتوانید در کدهایتان از آنها بارها و بارها استفاده کنید. به عنوان مثال قطع کدی برای border-radius ایجاد کنید ، (همانطور که میدانید border-radius برای مرورگرهای مختلف ، حالتهای مختلفی تعریف میشود.). برای ایجاد mixin ، در ابتدای قطع کد از @mixin استفاده نمایید و برای استفاده ازآن ، از @include استفاده نمایید:
@mixin cssProperty $yourCustomName{ … Your css properties… }
نمونه کد:
ایجاد mixin: @mixin border-radius($radius) { -webkit-border-radius: $radius; -moz-border-radius: $radius; -ms-border-radius: $radius; -o-border-radius: $radius; border-radius: $radius; } استفاده از mixin: .box { @include border-radius(10px); }
Extend/Inheritance -5 :
@XETEND به شما این امکان را میدهد تا بخشی از Property های یک selector را برای استفاده در selector های دیگر به اشتراک بگذارید:
.message { border: 1px solid #ccc; padding: 10px; color: #333; } .success { @extend .message; border-color: green; }
کدها بعد از تولید شدن به صورت زیر دیده میشوند:
.message, .success { border: 1px solid #cccccc; padding: 10px; color: #333; } .success { border-color: green; }
6- Operators :
می توانید از عملگرهای ضرب و تقسیم و جمع و تفریق در کدهای CSS خود استفاده نمایید:
article[role="main"] { float: left; width: 600px / 960px * 100%; }
نصب SASS :
حال که با SASS آشنا شدید ، انگیزه کافی برای دانستن روش نصب و استفاده آن خواهید داشت. برای استفاده از SASS می توانید از نرم افزارهایی که برای ویندوز ، مک و لینوکس وجود دارند، استفاده کنید از جمله این نرم افزارها :
CodeKit , Compass.app , Hammer , Koala , LiveReload , Mixture , Prepros , Prepros Pro , Scout
روش دیگر استفاده از command line میباشد:
چنانچه سیستم عامل شما ویندوز میباشد، برای استفاده از sass ابتدا باید rubby را نصب نمایید. سپس در Cmd خط زیر را اجرا کنید:
gem install sass
چنانچه به خطایی برخوردید، ابتدا gem توسط sudo را نصب کنید:
sudo gem install sass
سپس توسط خط زیر چک کنید که SASS نصب شده است یا خیر:
sass -v
Sass 3.2.12 (Media Mark)
برای کسب اطلاعات بیشتر و روش نصب در سایر سیستم عاملها به این لینک مراجعه نمایید.
SassScript :
فایل SASS اسکریپتی برای اجرای یک سری از فانکشنها دارد، از جمله :
- rgb($red, $green, $blue) /* برای ایجاد کد رنگ rgb */
برای مشاهده لیست کامل این فانکشنها به این لینک مراجعه کنید.
منبع
ایجاد یک Repository در پروژه برای دستورات EF
زمانیکه ToList، First و امثال آن روی این عبارت فراخوانی شود تبدیل به SQL شده و سپس بر روی بانک اطلاعاتی اجرا میشود. به این deferred execution یا اجرای به تعویق افتاده گفته میشود.
اگر این عبارت را در اختیار لایههای دیگر قرار دهید، یعنی انتهای کار را بازگذاشتهاید و حد و حدود سیستم شما مشخص نیست. شما اگر IQueryable بازگشت دهید، در لایهای دیگر میشود یک join روی آن نوشت و اطلاعات چندین جدول دیگر را استخراج کرد؛ درحالیکه نام متد شما GetUsers بوده. بنابراین بهتر است به صورت صریح اطلاعات را به شکل List بازگشت دهید، تا انتهای کار باز نمانده و طراحی شما نشتی نداشته باشد.
در این قسمت به پیاده سازی و توضیح مدلهای انجمن خواهیم پرداخت. قبل از شروع پیشنهاد میکنم مقالات قبلی را مطالعه کنید.
همکاران این قسمت:
سلمان معروفی
سید مجتبی حسینی
پیشنیاز این قسمت:
مقالات SQL Antipattern
سعی کردیم چندین پروژهی سورس باز را هم بررسی کنیم و در نهایت کاملترین و بهترین روش را پیاده سازی کنیم. NForum ، MyBB ، MVCForum ، بخش CMS مربوط به SmartStore و ساختار دیتابیس StackOverFlow ازجملهی آنها هستند.
/// <summary> /// Represents the Forum /// </summary> public class Forum { #region Properties /// <summary> /// gets or sets Id that Identify Forum /// </summary> public virtual long Id { get; set; } /// <summary> /// gets or sets Forum's title /// </summary> public virtual string Title { get; set; } /// <summary> /// gets or sets Description of forum /// </summary> public virtual string Description { get; set; } /// <summary> /// gets or sets value indicating Custom Slug /// </summary> public virtual string SlugUrl { get; set; } /// <summary> /// gets or sets order for display forum /// </summary> public virtual long DisplayOrder { get; set; } /// <summary> /// Indicating This Forum is Active or Not /// </summary> public virtual bool IsActive { get; set; } /// <summary> /// Indicating This Forum is Close or Not /// </summary> public virtual bool IsClose { get; set; } /// <summary> /// Indicating This Forum is Private or Not /// </summary> public virtual bool IsPrivate { get; set; } /// <summary> /// sets or gets password for login to Private forums /// </summary> public virtual string PasswordHash { get; set; } /// <summary> /// sets or gets depth of forum in tree structure of forums /// </summary> public virtual int Depth { get; set; } /// <summary> /// sets or gets Count of posts That they are Approved /// </summary> public virtual long ApprovedPostsCount { get; set; } /// <summary> /// sets or gets Count of topics That they are Approved /// </summary> public virtual long ApprovedTopicsCount { get; set; } /// <summary> /// Gets or sets the id of last topic /// </summary> public virtual long LastTopicId { get; set; } /// <summary> /// gets or sets date of creation of last topic /// </summary> public virtual DateTime? LastTopicCreatedOn { get; set; } /// <summary> /// gets or sets title of last topic /// </summary> public virtual string LastTopicTitle { get; set; } /// <summary> /// gets or sets creator of last topic /// </summary> public virtual string LastTopicCreator { get; set; } /// <summary> /// gets or sets id of creator that create last topic /// </summary> public virtual long LastTopicCreatorId { get; set; } /// <summary> /// Indicate in this Forum Moderate Topics Before Display /// </summary> public virtual bool ModerateTopics { get; set; } /// <summary> /// Indicate in this Forum Moderate Posts Before Dipslay /// </summary> public virtual bool ModeratePosts { get; set; } /// <summary> /// gets or sets Count of posts that they are UnApproved /// </summary> public virtual long UnApprovedPostsCount { get; set; } /// <summary> /// gets or sets Count of topics that they are UnApproved /// </summary> public virtual long UnApprovedTopicsCount { get; set; } /// <summary> /// gets or sets Rowversion /// </summary> public virtual byte[] RowVersion { get; set; } /// <summary> /// gets or sets icon name with size 200*200 px for snippet /// </summary> public virtual string SocialSnippetIconName { get; set; } /// <summary> /// gets or sets title for snippet /// </summary> public virtual string SocialSnippetTitle { get; set; } /// <summary> /// gets or sets description for snippet /// </summary> public virtual string SocialSnippetDescription { get; set; } /// <summary> /// gets or sets path for tree structure antipattern (1/3/4/23) /// </summary> public virtual string Path { get; set; } /// <summary> /// Indicate this forum inherit moderators from parent forum /// </summary> public virtual bool IsModeratorsInherited { get; set; } /// <summary> /// gets or set datetime that Last Post is Created In this forum. used for ForumTracking /// </summary> public virtual DateTime? LastPostCreatedOn { get; set; } #endregion #region NavigationProperties /// <summary> /// sets or gets identifier forum's parent /// </summary> public virtual long? ParentId { get; set; } /// <summary> /// sets or gets forum's parent /// </summary> public virtual Forum Parent { get; set; } /// <summary> /// sets or gets sub forums of forum /// </summary> public virtual ICollection<Forum> Children { get; set; } /// <summary> /// set or get topics of forum /// </summary> public virtual ICollection<ForumTopic> Topics { get; set; } /// <summary> /// get or set moderators of this forum /// </summary> public virtual ICollection<ForumModerator> Moderators { get; set; } /// <summary> /// get or set Subscriptions List /// </summary> public virtual ICollection<User> Subscribers { get; set; } /// <summary> /// get or set Announcements Collection of this Forum /// </summary> public virtual ICollection<ForumAnnouncement> Announcements { get; set; } /// <summary> /// get or set Trackers List Of this Forum /// </summary> public virtual ICollection<ForumTracker> Trackers { get; set; } /// <summary> /// get or set Posts List that Posted in this forum for increase Performance for get Posts Count /// </summary> public virtual ICollection<ForumPost> Posts { get; set; } /// <summary> /// get or set /// </summary> public virtual ICollection<ForumTopicTracker> TopicTrackers { get; set; } #endregion
- IsActive : مشخص کنندهی این است که در این انجمن امکان ارسال تاپیک و پست وجود دارد و در صورت false بودن این خصوصیت، بر تمام زیر انجمنها هم اعمال خواهد شد و برای زمانی مفید است که میخواهیم برای مدتی به هر دلیل خاصی امکان ارسال تاپیک و پست را برای انجمن خاصی، ندهیم.
- IsColsed : خصوصت اولی که مطرح شد اگر مقدار false بگیرد، همچنان کاربران میتوانند تایپکها و پستهای قبلی را مشاهده و مطالعه کنند. ولی با مقدار دهی این خصوصیت با مقدار false، امکان کلیهی فعالیتها و مشاهدهای را از محتوای این انجمن و زیر انجمنهای آن نخواهیم داشت.
- IsPrivate : برای مواقعی که لازم است برای انجمن خاصی کلمهی عبور در نظر بگیریم تا افراد خاص که کلمهی عبور آن را دارند بتوانند در آن انجمن فعالیت کنند، در نظر گرفته شده است.
- ApprovedPostsCount , UnApprovedPostsCount,ApprovedTopicsCount,UnApprovedTopicsCount : برای بالا بردن کارآیی سیستم به مانند مدلهای قبل در نظر گرفته شدهاند.
- LastTopicId, LastTopicTitle , LastTopicCreator , LastTopicCreatorId , LastTopicCreatedOn: همچنین برای افزایش کارآیی سیستم و نمایش به عنوان قسمتی از مشخصات قابل مشاهده از هر انجمن، در نظر گرفته شدهاند.
- Depth : برای نشان دادن عمق گره در درخت استفاده میشود که هنگام درج انجمن، این مورد از نتیجهی جمع عمق پدر انتخاب شده و یک، به دست خواهد آمد. این مورد هنگام واکشی برای مثال 4 سطح اول برای نمایش آنها در صفحهی اول انجمن به صورت سلسله مراتبی خیلی مفید خواهد بود.
- Path : برای استفاده از SQL Antipattern شمارش مسیر در نظر گرفته شده است. این مورد جزء Best Practiceها میتواند باشد. چون هم با استفاده از ساختار خود ارجاع، درخت خود را داریم و با این Antipattern کوئریهای مربوط به درخت خیلی راحت خواهد بود.
- IsModeratorsInherited : اگر لازم است مدیران انجمن، پدر را به عنوان مدیر خود قبول کنند، این خصوصیت مقدار true خواهد گرفت.
- Subscribers : هر انجمنی میتواند یکسری مشترک نیز داشته باشد (به منظور اطلاع رسانی با درج یک تاپیک جدید در خود انجمن یا زیر انجمنهای آن) .
- Posts : به منظور افزایش کارایی هنگام محاسبه تعداد پستهای ارسالی در یک انجمن ، در نظر گرفته شده است.
- TopicTrackers : در مقاله بعد توضیح داده خواهد شد.
- LastPostCreatedOn : به منظور استفاده از آن برای سیستم Tracking انجمنها استفاده خواهد شد .
/// <summary> /// Represents The Moderator For Forum /// </summary> public class ForumModerator { #region NavigationProperties /// <summary> /// gets or sets Forum /// </summary> public virtual Forum Forum { get; set; } /// <summary> /// gets or sets identifier of forum /// </summary> public virtual long ForumId { get; set; } /// <summary> /// gets or sets user that moderate forum /// </summary> public virtual User Moderator { get; set; } /// <summary> /// gets or sets id of user that moderate forum /// </summary> public virtual long ModeratorId { get; set; } /// <summary> /// gets or sets permission of user that moderate forum /// </summary> public virtual ForumModeratorPermissions Permissions { get; set; } /// <summary> /// indicate moderator's permissions in this forum apply with /// </summary> public virtual bool ApplyChildren { get; set; } #endregion } [Flags] public enum ForumModeratorPermissions { CanEditPosts=1, CanDeletePosts=2, CanManageTopics=4, CanOpenCloseTopics=8, ... }
- ApplyChildren : برای اعمال دسترسیهای مدیریتی کاربر x به زیر انجمنهای انجمن y البته اگر خصوصیت IsModeratorsInherited زیر انجمنهای مورد نظر با مقدار true مقدار دهی شده باشد.
- Permissions : از نوع ForumModeratorPermissions و نگهدارنده دسترسیهای کاربر x به عنوان مدیر انجمن y، میباشد.
- نکته : برای این مدل آی دی در نظر گرفته نشده است و از کلید مرکب متشکل از ForumId و ModeratorId استفاده خواهیم کرد.
/// <summary> /// Represents the Announcement that shown Top Of Forums /// </summary> public class ForumAnnouncement { #region Ctor /// <summary> /// create one instance of <see cref="ForumAnnouncement"/> /// </summary> public ForumAnnouncement() { Id = SequentialGuidGenerator.NewSequentialGuid(); CreatedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets Identifier /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets DateTime That this Announcement Will be Shown /// </summary> public virtual DateTime StartOn { get; set; } /// <summary> /// gets or sets DateTime That this Announcement Will be Finished /// </summary> public virtual DateTime? ExpireOn { get; set; } /// <summary> /// gets or sets Content of this Announcement /// </summary> public virtual string Message { get; set; } /// <summary> /// Indicate this Announcement Will be shown on Children Forums /// </summary> public virtual bool ApplyChildren { get; set; } /// <summary> /// gets or sets datetime that this record created /// </summary> public virtual DateTime CreatedOn { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets Forum that associated With this Announcement /// </summary> public virtual Forum Forum { get; set; } /// <summary> /// gets or sets Identifier of Forum that associated With this Announcement /// </summary> public virtual long ForumId { get; set; } #endregion }
- ApplyChildren : برای مشخص کردین نمایش این اعلان در زیر انجمنهای انجمن مورد نظر
- ExpireOn : به این دلیل نال پذیر در نظر گرفته شده است که اگر لازم بود، در زمان مشخصی به پایان نرسد و با null مقدار دهی شود.
/// <summary> /// Represents a base class for AuditLog /// </summary> public abstract class AuditBaseEntity { #region Properties /// <summary> /// sets or gets identifier /// </summary> public virtual long Id { get; set; } /// <summary> /// gets or sets datetime that is created /// </summary> public virtual DateTime CreatedOn { get; set; } /// <summary> /// gets or sets datetime that is modified /// </summary> public virtual DateTime? LastModifiedOn { get; set; } /// <summary> /// gets or sets reason of Last Update for increase performance /// </summary> public virtual string LastModifyReason { get; set; } /// <summary> /// gets or sets displayName of Last Modifier for increase performance /// </summary> public virtual string LastModifier{ get; set; } /// <summary> /// indicate this entity is Locked for Modify /// </summary> public virtual bool ModifyLocked { get; set; } /// <summary> /// gets or sets rowversion for synchronization problem /// </summary> public virtual byte[] RowVersion { get; set; } /// <summary> /// gets or sets count of this content's Updates /// </summary> public virtual int ModifyCount { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets creator of this record /// </summary> public virtual User Creator { get; set; } /// <summary> /// gets or sets creator's Id of this record /// </summary> public virtual long CreatorId { get; set; } #endregion }
/// <summary> /// Represents the Topic in the Forums /// </summary> public class ForumTopic { #region Ctor /// <summary> /// create one instance of <see cref="ForumTopic"/> /// </summary> public ForumTopic() { CreatedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// sets or gets identifier /// </summary> public virtual long Id { get; set; } /// <summary> /// gets or sets datetime that is created /// </summary> public virtual DateTime CreatedOn { get; set; } /// <summary> /// gets or sets Title Of this topic /// </summary> public virtual string Title { get; set; } /// <summary> /// gets or sets name of tags that assosiated with /// this content fo increase performance /// </summary> public virtual string TagNames { get; set; } /// <summary> /// indicate this topic is Sticky and will be shown top of forum /// </summary> public virtual bool IsSticky { get; set; } /// <summary> /// indicate this topic is closed /// </summary> public virtual bool IsClosed { get; set; } /// <summary> /// gets or sets identifier of last post in this topic /// </summary> public virtual long LastPostId { get; set; } /// <summary> /// gets or sets identifier of Last user that post in this topic /// </summary> public virtual long LastPosterId { get; set; } /// <summary> /// gets or sets title of last Post in this topic /// </summary> public virtual string LastPostTitle { get; set; } /// <summary> /// gets or sets displayName of user that create lastpost in this topic /// </summary> public virtual string LastPoster { get; set; } /// <summary> /// gets or sets datetime that last post posted in this topic /// </summary> public virtual DateTime? LastPostCreatedOn { get; set; } /// <summary> /// indicate this topic is approved /// </summary> public virtual bool IsApproved { get; set; } /// <summary> /// indicate this topic is type of Announcements and shown in Annoucements sections /// </summary> public virtual bool IsAnnouncement { get; set; } /// <summary> /// gets or sets viewed count /// </summary> public virtual long ViewCount { get; set; } /// <summary> /// gets or sets count of posts that they are approved /// </summary> public virtual int ApprovedPostsCount { get; set; } /// <summary> /// gets or sets count of posts that they are Unapproved /// </summary> public virtual int UnApprovedPostsCount { get; set; } /// <summary> /// gets or sets specifications of this topic's rating /// </summary> public virtual Rating Rating { get; set; } /// <summary> /// gets or sets datetime that this topic closed /// </summary> public virtual DateTime? ClosedOn { get; set; } /// <summary> /// gets or sets reason that this topic colsed /// </summary> public virtual string ClosedReason { get; set; } /// <summary> /// gets or sets count of reports /// </summary> public virtual int ReportsCount { get; set; } /// <summary> /// indicate the posts of this topic should be Moderate Before Dipslay /// </summary> public virtual bool ModeratePosts { get; set; } /// <summary> /// gets or sets Level of this topic /// </summary> public virtual ForumTopicLevel Level { get; set; } /// <summary> /// gets or sets type of this topic /// </summary> public virtual ForumTopicType Type { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets Collection of tags that associated with this topic /// </summary> public virtual ICollection<Tag> Tags { get; set; } /// <summary> /// gets or sets forum /// </summary> public virtual Forum Forum { get; set; } /// <summary> /// gets or sets identifier of Forum /// </summary> public virtual long ForumId { get; set; } /// <summary> /// gets or sets Posts Of this topic /// </summary> public virtual ICollection<ForumPost> Posts { get; set; } /// <summary> /// get or set Subscriptions List /// </summary> public virtual ICollection<User> Subscribers { get; set; } /// <summary> /// get or set Trackkers list of this Topic /// </summary> public virtual ICollection<ForumTopicTracker> Trackers { get; set; } /// <summary> /// gets or sets creator of this record /// </summary> public virtual User Creator { get; set; } /// <summary> /// gets or sets creator's Id of this record /// </summary> public virtual long CreatorId { get; set; } #endregion } public enum ForumTopicType { Non, Tutorial, Conversation, Question, News, Article } public enum ForumTopicLevel { Professional, Intermediate, Beginner }
- LastPostId , LastPosterId, LastPoster , LastPostTitle ,LastPostCreatedOn: برای افزایش کارآیی سیستم در نظر گرفته شدهاند.
- ModeratePosts : اگر لازم است پستهای یک تاپیک خاص، قبل از نمایش مدیریت شوند، با true مقدار دهی خواهد شد.
- Tags : لیستی از برچسبها که برای اعمال رابطهی چند به چند با مدل برچسبهای معرفی شدهی در مقاله اول، در نظر گرفته شده است.
- Posts : در هر تاپیکی یک سری پست به عنوان جوابهای آن ارسال خواهد شد.
- Subscribers : به مانند انجمنها، تاپیکهای ما هم میتوانند یک سری مشترک داشته باشند، تا از تغییرات این تاپیک مطلع شوند .
- Trackers : مربوط به سیستم Tracking تاپیک میباشد. و در مقاله بعد توضیح داده خواهد شد.
حتما لازم خواهد بود تاریخچهی تغییرات برای پستهای ارسالی ذخیره شوند؛ در مقالهی بعدی به این موضوع هم خواهیم پرداخت.
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<TargetFrameworks>netstandard2.0;net462;</TargetFrameworks>
runs-on: windows-2019