در این قسمت میخواهیم روش تغییر رنگهای قالبهای پیشفرض Angular Material را به همراه تغییر پویای آنها در زمان اجرا، بررسی کنیم. همچنین Angular Material از راست به چپ نیز به خوبی پشتیبانی میکند که مثالی از آنرا در ادامه بررسی خواهیم کرد.
بررسی ساختار یک قالب Angular Material
قالب، مجموعهای از رنگها است که به کامپوننتهای Angular Material اعمال میشود. هر قالب از چندین جعبهرنگ یا palette تشکیل میشود:
- primary palette: به صورت گستردهای در تمام کامپوننتها مورد استفادهاست.
- accent palette: به المانهای تعاملی انتساب داده میشود.
- warn palette: برای نمایش خطاها و اخطارها بکار میرود.
- foreground palette: برای متون و آیکنها استفاده میشود.
- background palette: برای پسزمینهی المانها بکار میرود.
روش انتخاب این جعبه رنگها نیز به صورت زیر است:
در Angular Material تمام قالبها استاتیک بوده و در زمان کامپایل برنامه به صورت خودکار به آن اضافه میشوند. به همین جهت برنامه نیازی به تشکیل این اجزا و کامپایل یک قالب را در زمان آغاز آن ندارد.
همانطور که در قسمت اول این سری نیز بررسی کردیم، بستهی Angular Material به همراه چندین قالب از پیش طراحی شدهاست (قالبهای از پیش آمادهی متریال را در پوشهی node_modules\@angular\material\prebuilt-themes میتوانید مشاهده کنید) و در حین اجرای برنامه تنها یکی از آنها که در فایل styles.css ذکر شدهاست، مورد استفاده قرار میگیرد.
اگر نیاز به سفارشی سازی بیشتری وجود داشته باشد، میتوان قالبهای ویژهی خود را نیز طراحی کرد. این قالب جدید باید mat-core() sass mixin را import کند که حاوی تمام شیوهنامههای مشترک بین کامپوننتها است. این مورد باید تنها یکبار به کل برنامه الحاق شود تا حجم آنرا بیش از اندازه زیاد نکند. سپس این قالب سفارشی، جعبه رنگهای خاص خودش را معرفی میکند. در ادامه این جعبه رنگها توسط توابع mat-light-theme و یا mat-dark-theme ترکیب شده و مورد استفاده قرار میگیرند. سپس این قالب را include خواهیم کرد. به این ترتیب یک قالب سفارشی Angular Material، چنین طرحی را دارد:
اعداد و ارقامی را که در اینجا ملاحظه میکنید، در سیستم رنگهای طراحی متریال به darker hue و lighter hue تفسیر میشوند. همچنین امکان سفارشی سازی تایپوگرافی آن نیز وجود دارد.
ایجاد یک قالب سفارشی جدید Angular Material
برای ایجاد یک قالب سفارشی نیاز است از فایلهای sass استفاده کرد. بنابراین بهترین روش ایجاد برنامههای Angular Material در ابتدای کار، ذکر صریح نوع style مورد استفاده به sass است:
اگر اینکار را انجام ندادهایم و حالت پیشفرض پروژه همان css است، مهم نیست. میتوان فایل قالب سفارشی را در یک فایل با پسوند custom.theme.scss نیز در پوشهی src قرار داد و سپس آنرا در فایل angular.json مشخص کرد تا به صورت css کامپایل شده و مورد استفاده قرار گیرد:
بدیهی است اگر از ابتدا style=sass را تنظیم کرده بودیم، نیازی به ایجاد این فایل اضافی نبود و همان styles.scss اصلی را میشد ویرایش کرد و در این حالت فایل angular.json بدون تغییر باقی میماند.
پس از افزودن و تنظیم فایل custom.theme.scss، به فایل styles.css مراجعه کرده و قالب فعلی را به صورت comment در میآوریم:
سپس فایل src\custom.theme.scss را به صورت زیر تکمیل میکنیم:
که در اینجا شامل مراحل import فایلهای پایهی Angular Material، تعریف جعبه رنگ جدید، ترکیب آنها و در نهایت include آنها میباشد.
در اینجا روش تعریف یک قالب دوم (alternate-theme) را نیز مشاهده میکنید. علت تعریف قالب دوم در همین فایل جاری، کاهش حجم نهایی برنامه است. از این جهت که اگر alternate-themeها را در فایلهای scss دیگری قرار دهیم، مجبور به import تعاریف اولیهی قالبهای Angular Material در هرکدام به صورت جداگانهای خواهیم بود که حجم قابل ملاحظهای را به خود اختصاص میدهند. به همین جهت قالبهای دیگر را نیز در همینجا به صورت کلاسهای ثانویه تعریف خواهیم کرد.
در این حالت روش استفادهی از این قالب ثانویه به صورت زیر میباشد:
پس از افزودن فایل src\custom.theme.scss به برنامه، اگر آنرا اجرا کنیم به خروجی زیر خواهیم رسید:
افزودن امکان انتخاب پویای قالبها به برنامه
قصد داریم به منوی برنامه که اکنون گزینهی new contact را به همراه دارد، گزینهی toggle theme را هم جهت تغییر پویای قالب اصلی برنامه اضافه کنیم. به همین جهت فایل toolbar.component.html را گشوده و به صورت زیر تغییر میدهیم:
در اینجا دکمهای به منو اضافه شدهاست که سبب صدور رخدادی به والد آن یا همان sidenav خواهد شد. علت اینجا است که تغییر قالب را در sidenav، که در برگیرندهی router-outlet است، میتوان به کل برنامه اعمال کرد.
بنابراین جهت تبادل اطلاعات بین toolbar و sidenav از یک رخداد استفاده خواهیم کرد. برای این منظور فایل toolbar.component.ts را گشوده و این رخداد را به آن اضافه میکنیم:
پس از این تعریف، به sidenav.component.html مراجعه کرده و به این رخداد گوش فرا میدهیم:
متد toggleTheme را نیز به صورت زیر به sidenav.component.ts اضافه میکنیم:
در اینجا یک خاصیت عمومی boolean را با کلیک بر روی گزینهی منوی Toggle theme، به true و یا false تنظیم میکنیم. اکنون از این مقدار جهت تغییر css قالب sidenav استفاده خواهیم کرد:
به این ترتیب اگر مقدار isAlternateTheme مساوی true باشد، کلاس alternate-theme به قالب sidenav به صورت پویا اعمال خواهد شد و برعکس. در تصویر زیر نمونهای از تغییر پویای قالب برنامه را مشاهده میکنید:
افزودن پشتیبانی از راست به چپ به قالب برنامه
اگر به mat-sidenav-container ویژگی dir=rtl را اضافه کنیم، قالب برنامه راست به چپ خواهد شد. در ادامه میخواهیم شبیه به حالت تغییر پویای قالب سایت، گزینهای را به منوی برنامه جهت تغییر جهت برنامه نیز اضافه کنیم. برای این منظور به قالب toolbar.component.html مراجعه کرده و گزینهی Toggle dir را به آن اضافه میکنیم:
سپس این رخداد را که قرار است در نهایت به sidenav منتقل شود، به صورت زیر به toolbar.component.ts اضافه میکنیم:
اکنون در sidenav.component.html به این رخداد گوش فرا خواهیم داد:
متد toggleDir در sidenav.component.ts به صورت زیر پیاده سازی میشود:
و در نهایت این جهت را به mat-sidenav-container در فایل sidenav.component.html اعمال میکنیم:
در تصویر زیر نمونهای از تغییر پویای جهت برنامه را مشاهده میکنید:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-06.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
بررسی ساختار یک قالب Angular Material
قالب، مجموعهای از رنگها است که به کامپوننتهای Angular Material اعمال میشود. هر قالب از چندین جعبهرنگ یا palette تشکیل میشود:
- primary palette: به صورت گستردهای در تمام کامپوننتها مورد استفادهاست.
- accent palette: به المانهای تعاملی انتساب داده میشود.
- warn palette: برای نمایش خطاها و اخطارها بکار میرود.
- foreground palette: برای متون و آیکنها استفاده میشود.
- background palette: برای پسزمینهی المانها بکار میرود.
روش انتخاب این جعبه رنگها نیز به صورت زیر است:
<mat-card> Main Theme: <button mat-raised-button color="primary"> Primary </button> <button mat-raised-button color="accent"> Accent </button> <button mat-raised-button color="warn"> Warning </button> </mat-card>
همانطور که در قسمت اول این سری نیز بررسی کردیم، بستهی Angular Material به همراه چندین قالب از پیش طراحی شدهاست (قالبهای از پیش آمادهی متریال را در پوشهی node_modules\@angular\material\prebuilt-themes میتوانید مشاهده کنید) و در حین اجرای برنامه تنها یکی از آنها که در فایل styles.css ذکر شدهاست، مورد استفاده قرار میگیرد.
اگر نیاز به سفارشی سازی بیشتری وجود داشته باشد، میتوان قالبهای ویژهی خود را نیز طراحی کرد. این قالب جدید باید mat-core() sass mixin را import کند که حاوی تمام شیوهنامههای مشترک بین کامپوننتها است. این مورد باید تنها یکبار به کل برنامه الحاق شود تا حجم آنرا بیش از اندازه زیاد نکند. سپس این قالب سفارشی، جعبه رنگهای خاص خودش را معرفی میکند. در ادامه این جعبه رنگها توسط توابع mat-light-theme و یا mat-dark-theme ترکیب شده و مورد استفاده قرار میگیرند. سپس این قالب را include خواهیم کرد. به این ترتیب یک قالب سفارشی Angular Material، چنین طرحی را دارد:
@import '~@angular/material/theming'; @include mat-core(); $candy-app-primary: mat-palette($mat-indigo); $candy-app-accent: mat-palette($mat-pink, A200, A100, A400); $candy-app-warn: mat-palette($mat-red); $candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn); @include angular-material-theme($candy-app-theme);
ذکر جعبه رنگ اخطار در اینجا اختیاری است و اگر ذکر نشود به قرمز تنظیم خواهد شد.
ایجاد یک قالب سفارشی جدید Angular Material
برای ایجاد یک قالب سفارشی نیاز است از فایلهای sass استفاده کرد. بنابراین بهترین روش ایجاد برنامههای Angular Material در ابتدای کار، ذکر صریح نوع style مورد استفاده به sass است:
ng new MyProjectName --style=sass
"styles": [ "node_modules/material-design-icons/iconfont/material-icons.css", "src/styles.css", "src/custom.theme.scss" ],
پس از افزودن و تنظیم فایل custom.theme.scss، به فایل styles.css مراجعه کرده و قالب فعلی را به صورت comment در میآوریم:
/* @import "~@angular/material/prebuilt-themes/indigo-pink.css"; */ body { margin: 0; }
@import '~@angular/material/theming'; @include mat-core(); $my-app-primary: mat-palette($mat-blue-grey); $my-app-accent: mat-palette($mat-pink, 500, 900, A100); $my-app-warn: mat-palette($mat-deep-orange); $my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn); @include angular-material-theme($my-app-theme); .alternate-theme { $alternate-primary: mat-palette($mat-light-blue); $alternate-accent: mat-palette($mat-yellow, 400); $alternate-theme: mat-light-theme($alternate-primary, $alternate-accent); @include angular-material-theme($alternate-theme); }
در اینجا روش تعریف یک قالب دوم (alternate-theme) را نیز مشاهده میکنید. علت تعریف قالب دوم در همین فایل جاری، کاهش حجم نهایی برنامه است. از این جهت که اگر alternate-themeها را در فایلهای scss دیگری قرار دهیم، مجبور به import تعاریف اولیهی قالبهای Angular Material در هرکدام به صورت جداگانهای خواهیم بود که حجم قابل ملاحظهای را به خود اختصاص میدهند. به همین جهت قالبهای دیگر را نیز در همینجا به صورت کلاسهای ثانویه تعریف خواهیم کرد.
در این حالت روش استفادهی از این قالب ثانویه به صورت زیر میباشد:
<mat-card class="alternate-theme"> Alternate Theme: <button mat-raised-button color="primary"> Primary </button> <button mat-raised-button color="accent"> Accent </button> <button mat-raised-button color="warn"> Warning </button> </mat-card>
پس از افزودن فایل src\custom.theme.scss به برنامه، اگر آنرا اجرا کنیم به خروجی زیر خواهیم رسید:
افزودن امکان انتخاب پویای قالبها به برنامه
قصد داریم به منوی برنامه که اکنون گزینهی new contact را به همراه دارد، گزینهی toggle theme را هم جهت تغییر پویای قالب اصلی برنامه اضافه کنیم. به همین جهت فایل toolbar.component.html را گشوده و به صورت زیر تغییر میدهیم:
<mat-menu #menu="matMenu"> <button mat-menu-item (click)="openAddContactDialog()">New Contact</button> <button mat-menu-item (click)="toggleTheme.emit()">Toggle theme</button> </mat-menu>
بنابراین جهت تبادل اطلاعات بین toolbar و sidenav از یک رخداد استفاده خواهیم کرد. برای این منظور فایل toolbar.component.ts را گشوده و این رخداد را به آن اضافه میکنیم:
export class ToolbarComponent implements OnInit { @Output() toggleTheme = new EventEmitter<void>();
<app-toolbar (toggleTheme)="toggleTheme()" (toggleSidenav)="sidenav.toggle()"></app-toolbar>
export class SidenavComponent { isAlternateTheme = false; toggleTheme() { this.isAlternateTheme = !this.isAlternateTheme; } }
<mat-sidenav-container fxLayout="row" class="app-sidenav-container" fxFill [class.alternate-theme]="isAlternateTheme">
افزودن پشتیبانی از راست به چپ به قالب برنامه
اگر به mat-sidenav-container ویژگی dir=rtl را اضافه کنیم، قالب برنامه راست به چپ خواهد شد. در ادامه میخواهیم شبیه به حالت تغییر پویای قالب سایت، گزینهای را به منوی برنامه جهت تغییر جهت برنامه نیز اضافه کنیم. برای این منظور به قالب toolbar.component.html مراجعه کرده و گزینهی Toggle dir را به آن اضافه میکنیم:
<mat-menu #menu="matMenu"> <button mat-menu-item (click)="openAddContactDialog()">New Contact</button> <button mat-menu-item (click)="toggleTheme.emit()">Toggle theme</button> <button mat-menu-item (click)="toggleDir.emit()">Toggle dir</button> </mat-menu>
export class ToolbarComponent implements OnInit { @Output() toggleDir = new EventEmitter<void>();
<app-toolbar (toggleDir)="toggleDir()" (toggleTheme)="toggleTheme()" (toggleSidenav)="sidenav.toggle()"></app-toolbar>
export class SidenavComponent implements OnInit, OnDestroy { dir = "ltr"; toggleDir() { this.dir = this.dir === "ltr" ? "rtl" : "ltr"; } }
<mat-sidenav-container fxLayout="row" class="app-sidenav-container" fxFill [dir]="dir" [class.alternate-theme]="isAlternateTheme">
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-06.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
نظرات مطالب
طبقه بندی Bad Code Smell ها
میتونید راه حل بهتری در مورد این دو سطر ارائه بدید (چون به عنوان code smell نامبرده شدن)؟
«استفاده از constantها برای کد کردن اطلاعات مانند USER_ADMIN_ROLE = 1
استفاده از constantهای رشتهای به عنوان نام فیلدها در آرایههای داده»
در کل توصیه شده بجای استفاده از magic numbers از ثوابت استفاده بشه. مثلا بجای نوشتن if(role==1) بهتر هست نوشته بشه if(role==UserAdminRole) که از هر لحاظ خوانایی بهتری داره نسبت به ذکر عدد 1 که مشخص نیست چی هست. بعلاوه مشکل تغییر این اعداد هم در کل پروژه صرفا با تغییر محل اصلی اونها قابل حل است و نگهداری رو سادهتر میکنند.
- نه الزاما. اگر چندین جا استفاده و تکرار میشود یا منطق طولانی دارد، روش «نوشتن HTML Helpers ویژه، به کمک امکانات Razor» میتواند مفید باشد.
- منطق قرار گرفته در View فقط باید کار «نهایی» نمایشی را انجام دهد. اگر در View مثلا مستقیما با دیتابیس کار میکنید، محاسبات مرتبط با فیلد خاصی را انجام میدهید و کلا هر منطقی که «نهایی» بودن نمایش اطلاعات را زیر سؤال ببرد، باید از View جدا شده و به کنترلر و زیرساخت آن منتقل شود.
- منطق قرار گرفته در View فقط باید کار «نهایی» نمایشی را انجام دهد. اگر در View مثلا مستقیما با دیتابیس کار میکنید، محاسبات مرتبط با فیلد خاصی را انجام میدهید و کلا هر منطقی که «نهایی» بودن نمایش اطلاعات را زیر سؤال ببرد، باید از View جدا شده و به کنترلر و زیرساخت آن منتقل شود.
با توجه به افزایش کاربرد jQuery و دیگر کتابخانههای جاوا اسکریپت در برنامههای تحت وب، یکی از چالشهای همیشگی برنامه نویسان، فشرده سازی فایلهای دربرگیرنده کدهای جاوا اسکریپت و شیوه نامهها می باشد. برای این منظور راههای مختلفی مانند استفاده از ابزارهای آنلاین مانند این + و این + وجود دارند. اما یک روش خودکار هم وجود دارد که در زمان Build پروژههای دات نت میتوان از آن بهره گرفت.
Microsoft Ajax Minifier
یک ابزار رایگان جهت فشرده سازی فایلهای جاوا اسکریپت و شیوه نامهها است. شما میتوانید این ابزار را از صفحه خانگی آن در سایت asp.net دریافت کنید. جهت استفاده از این ابزار میتوان از طریق خط فرمان عمل کرد. اما روش سادهتر که هدف اصلی این مطلب است به شرح زیر است:
1. در VisualStudio.NET از طریق منو به مسیر Tools, Options, Projects and Solutions بروید و گزینه Always show solution را تیک بزنید.
2. از Solution Explorer بر روی عنوان پروژه کلیک راست کرده و گزینه Unload Project را انتخاب نمایید.
3. مجدداً روی عنوان پروژه کلیک راست کرده و گزینه Edit را انتخاب کنید و دستورات زیر را قبل از بسته شدن تگ Project اضافه کنید:
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" /> <Target Name="AfterBuild"> <ItemGroup> <JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" /> </ItemGroup> <ItemGroup> <CSS Include="**\*.css" Exclude="**\*.min.css" /> </ItemGroup> <AjaxMin JsSourceFiles="@(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js" CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css" /> </Target>
4. دوباره بر روی عنوان پروژه کلیک راست کرده و گزینه Reload Project را انتخاب کنید.
توجه کنید که با این کار ما یک MSBuild task با عنوان ajaxmini به پروژه اضافه کردیم. این وظیفه که در زمان Build پروژه اجرا خواهد شد فایلهای جاوا اسکریپت را فشرده و با پسوند .min.js و همچنین فایلهای CSS را پس از فشرده سازی با پسوند .min.css در همان مسیر فایل مادر بطور خودکار ذخیره میکند.
نکته:
اگر به دستورات تنظیمات فوق نگاه دقیقتری بیندازیم، متوجه عبارات Include و Exclude می شویم. توسط این دو صفت شما میتوانید الگوهایی را جهت فشرده سازی و یا عدم فشرده سازی تعریف کنید. بدین معنا که توسط الگویهای ذکر شده در تنظیمات فوق از فشرده سازی فایلهای با پسوند .min.css و .min.js خودداری میشود.
در این شرایط در حین توسعه برنامه، شما میتوانید از فایلهای با کد خوانا استفاده نمایید و زمان انتشار و Build پروژه بصورت خودکار آنها را با فایلهای فشرده جایگزین کنید.
این ابزار تمامی فضاهای خالی، ';' و '{ }'های اضافی و توضیحات را از کدهای شما حذف میکند. متغیرها و توابع شما را به اسامی کوجکتر تغییر نام میدهد. و ...
همچنین شما از کتابخانه این پروژه میتوانید در زمان اجرا و سورس برنامه خود استفاده کنید. جهت اطلاعات بیشتر میتوانید به سایت مربوطه مراجعه نمایید.
احتمالا همیشه برای شما سؤال بوده است که مجوزهای گوناگون سورس باز با هم چه فرقی دارند، یا اینکه اگر روزی خواستم پروژهی خود را به صورت سورس باز ارائه کنم، کدامیک از مجوزهای موجود مناسبتر است و همچنین وقت مطالعه مقالات طولانی یا کتابهایی چند صد صفحهای در این مورد را نداشتهاید.
جدول زیر کار مقایسه این مجوزها (موارد رایجتر) را به صورت مختصر و مفید و بر اساس سؤالات رایج کاربران، انجام میدهد:
نام مجوز | آیا به کار مشتق شده از پروژه اصلی، میتوانم نامی دیگر بدهم؟ | آیا باید حتما کار مشتق شده سورس باز باقی بماند؟ | آیا میتوانم برای کار مشتق شده مجوزی جدید انتخاب کنم؟ | آیا میتوانم کار مشتق شده را بفروشم و کسب درآمد کنم؟ |
Apache License 2.0 | بله | خیر | بله | بله |
Common Development and Distribution License (CDDL) | بله | خیر | بله (به مجوزهای سازگار دیگری از همین دست) | بله |
GNU General Public License 2.0 (GPLv2) | بله، اما حتما باید لیست تغییرات انجام شده نسبت به پروژه اصلی را نیز ارائه بدهید. | بله | بله (به مجوزهای سازگار دیگری از همین دست یا توافق با نویسنده اصلی) | بله |
GNU Library General Public License (LGPL) | بله | بله، اما امکان استفاده از کتابخانههای کامپایل شده یک پروژه سورس باز تحت این مجوز در یک پروژه سورس بسته نیز وجود دارد. | بله (به مجوزهای سازگار دیگری از همین دست) | بله |
Microsoft Public License (Ms-PL) | بله، اما نمیتوانید از علامت تجاری خود استفاده کنید. | خیر | خیر | بله |
Microsoft Reciprocal License (Ms-RL) | بله، اما نمیتوانید از علامت تجاری خود استفاده کنید. | بله | خیر | بله |
Mozilla Public License 1.1 (MPL) | بله | خیر | خیر | بله |
BSD License | بله | خیر | بله | بله |
MIT License | بله | خیر | بله | بله |
همچنین لازم به ذکر است که
مجوزهای کار اصلی و کار مشتق شده هر دو باید ذکر شوند.
پسندیده است که از نویسندگان کار اصلی، نامبرده شده و قدردانی گردد.
هیچکدام از این مجوزها مسؤولیتی را در قبال کار انجام شده نمیپذیرند!
جهت مطالعه بیشتر:
http://khason.net/blog/open-source-licenses-comparison-table/
http://developer.kde.org/documentation/licensing/licenses_summary.html
http://en.wikipedia.org/wiki/Comparison_of_free_software_licences
در نگارشهای پیشین ASP.NET MVC با استفاده از Output Cache، امکان کش کردن خروجی یک اکشن متد، وجود دارد. مکانیزم Output Cache از ASP.NET Core حذف شدهاست؛ اما جایگزینهای قابل توجهی برای آن تدارک دیده شدهاند.
معرفی Response Cache
جایگزین ویژگی حذف شدهی OutputCache در ASP.NET Core، ویژگی جدیدی است به نام ResponseCache و هدف آن تنظیم هدرهای مرتبط با caching مخصوص HTTP Response ارائه شدهاست. به همین جهت با مکانیزم OutputCache قدیمی ASP.NET MVC که اطلاعات را در حافظهی سرور کش میکرد، کاملا متفاوت است.
البته قرار است میان افزار OutputCache را در نگارشهای آتی ASP.NET Core نیز ارائه کنند.
در اینجا مثالی را از نحوهی تعریف این ویژگی جدید، ملاحظه میکنید که در آن مقدار خاصیت مدت زمان کش شدن، برحسب ثانیه است. استفادهی از آن سبب خواهد شد تا هدر HTTP ذیل به خروجی از سرور اضافه شود:
یک نکته: این ویژگی را هم میتوان به کل کنترلر اعمال کرد و هم به یک اکشن متد خاص. اگر این ویژگی هم به کنترلر و هم به اکشن متدی در آن کنترلر اعمال شده باشد، تنظیمات در سطح متدها، تنظیمات در سطح کلاس را بازنویسی میکنند.
تعیین مکان کش شدن خروجی یک اکشن متد
در هدر فوق، عبارت public را مشاهده میکنید. این public بودن به این معنا است که امکان کش شدن این خروجی، توسط کش سرورهای اشتراکی بین راه هم وجود دارد.
اگر میخواهید این امکان را غیرفعال کنید، نیاز است این public به private تنظیم شود:
تنظیم Location فوق به Client به معنای private شدن هدر تنظیم شده و صرفا کش شدن خروجی، توسط کش مرورگر کاربر میباشد.
غیرفعال کردن کش شدن خروجی یک اکشن متد
اگر خواستید از کش شدن خروجی یک اکشن متد تحت هر حالتی جلوگیری کنید، مکان آنرا به None و NoStore آنرا به true تنظیم کنید:
این تنظیم سبب افزوده شدن یک چنین هدر HTTP ایی به خروجی از سرور میشود:
امکان تعریف پروفایلهای کش
بجای اینکه تنظیمات کش کردن تکراری را به انواع و اقسام اکشن متدها اعمال کنیم، میتوان برای آنها پروفایل ایجاد کرده و از نام این پروفایل، جهت به اشتراک گذاری تنظیمات استفاده کنیم. برای این منظور به کلاس آغازین برنامه مراجعه کرده و جایی که سرویس ASP.NET MVC را فعال سازی کردهاید، پروفایل کش جدیدی را تعریف کنید:
پس از آن برای استفادهی از این تنظیمات اشتراکی، فقط کافی است تا نام پروفایل مرتبطی را ذکر کنیم:
معرفی سرویس کش درون حافظهای
در نگارشهای پیشین ASP.NET، متدهایی برای کش کردن موقتی اطلاعات در حافظه و سپس بازیابی آنها وجود داشتند. در ASP.NET Core، این متدها توسط سرویس ارائه کنندهی IMemoryCache در اختیار برنامه قرار میگیرند. برای فعال سازی این سرویس جدید باید مراحل ذیل طی شوند:
الف) ابتدا بستهی Microsoft.Extensions.Caching.Memory را به لیست وابستگیهای پروژه در فایل project.json اضافه کنید:
ب) سپس به کلاس آغازین برنامه مراجعه کرده و سرویس آنرا معرفی و ثبت کنید:
ج) پس از آن سرویس پیاده سازی کنندهی IMemoryCache، در تمام اجزای برنامه در دسترس خواهد بود. برای مثال:
در مثال فوق، ابتدا وابستگی سرویس کش درون حافظهای، به سازندهی کنترلر تزریق شدهاست. تامین آن هم توسط سرویسی که در کلاس آغازین برنامه ثبت کردیم، انجام میشود. پس از آن در اکشن متد Hello، سعی کردهایم بر اساس کلید کشی که مشخص کردهایم، مقداری را بازیابی کنیم. اگر این مقدار وجود نداشته باشد، آنرا توسط متد Set تنظیم خواهیم کرد تا برای دفعات آتی فراخوانی این متد، مورد استفاده قرار گیرد.
تنظیمات منقضی شدن کش نیز به حالت absolute تنظیم شدهاست. یعنی پس از یک دقیقه حتما منقضی میشود. اگر فراخوانیهای این متد زیاد است، میتوان حالت منقضی شدن sliding را تنظیم کرد:
در این حالت اگر پیش از اتمام 5 دقیقهی تنظیم شده، درخواستی به سرور رسید، این کش برای 5 دقیقهی بعد نیز مجددا تمدید میشود.
اگر خواستیم تا این کش سر ساعت منقضی شود، اما در طی این یک ساعت به صورت sliding عمل کند، میتوان از ترکیب دو حالت مطلق و لغزشی استفاده کرد:
یک نکته: اگر فشار حافظهی سرور زیاد شود، مدیر حافظهی این کش، شروع به منقضی کردن آیتمهایی با حق تقدم پایین میکند. بالاترین حق تقدم را حالت NeverRemove ذیل دارد:
معرفی Tag Helpers مخصوص کش کردن قسمتی از صفحه
در ادامهی مبحث معرفی Tag Helpers، تعدادی از آنها جهت کش کردن محتوای قسمتی از صفحه، طراحی شدهاند:
تگ جدید cache محتوای دربرگیرندهی آنرا «در حافظهی سرور» کش میکند (و در پشت صحنه از همان کش درون حافظهای که پیشتر بحث شد، استفاده میکند). تگ cache در خروجی HTML نهایی مشاهده نمیشود و صرفا مفهومی سمت سرور است.
برای نمونه در مثال فوق، محتوای پارشال ویوو رندر شده و همچنین تاریخی که پس از آن نمایش داده شدهاست، به مدت 10 دقیقه در حافظهی سرور کش میشوند. اگر این زمان تنظیم نشود، تا زمانیکه برنامه در سرور مشغول به کار است، این قسمت منقضی نخواهد شد.
در اینجا اگر expires-after ذکر شده بود، یعنی پس از این مدت زمان، کش منقضی میشود.
اگر expires-on آن ذکر شود، میتوان تاریخ و زمان مشخصی را در اینجا ذکر کرد (برای مثال فردا ساعت 10، با فراخوانی DateTime.Today.AddDays).
همچنین میتوان از expires-sliding نیز استفاده کرد. به این معنا که اگر در طی مدتی خاص این صفحه درخواست نشد، آنگاه این کشی منقضی میشود.
همچنین در اینجا میتوان کش کردن را به ازای کاربران مختلف، کوئری استرینگهای مختلف و امثال آن انجام داد (با ارائهی محتوای متفاوتی به ازای پارامترهای مختلف):
در این حالت دیگر نیازی نیست تا نگران این باشیم که آیا محتوای قسمت کش شدهی از صفحه برای تمام کاربران در دسترس است یا خیر؟ در اینجا هر کاربر لاگین شدهی به سیستم، نگارش کش شدهی خاص خودش را دریافت میکند.
در اینجا به ازای پارامتر آیدی مسیریابی، نگارشهای مختلف کش شدهای از صفحه تامین میشوند. در اینجا میتوان لیستی از پارامترهای جدا شدهی با کاما را مشخص کرد.
امکان کش کردن محتوای صفحه به ازای کوئری استرینگهای مختلف تنظیم شده نیز وجود دارد.
در اینجا به ازای محتواهای مختلف کوکی خاصی به نام MyAppCookie، نگارشهای مختلف کش شدهای از صفحه ذخیره میشوند.
در اینجا میتوان به ازای هدرهای مختلف پروتکل HTTP نگارشهای کش شدهی متفاوتی را ارائه داد.
اگر خواستید کلید کش را خودتان تعیین کنید از vary-by استفاده کنید.
امکان ترکیب این موارد با هم نیز وجود دارد.
به علاوه چون زیر ساخت این Tag Helper همان Microsoft.Extensions.Caching.Memory است، امکان تنظیم حق تقدم حذف شدن آیتمهای کش شده نیز وجود دارد:
مبحث تکمیلی
امکان ذخیره سازی آیتمهای کش شده در بانک اطلاعاتی (بجای حافظهی فرار) نیز پیش بینی شدهاست که تحت عنوان «کش توزیع شده» در دسترس است.
Working with a Distributed Cache
معرفی Response Cache
جایگزین ویژگی حذف شدهی OutputCache در ASP.NET Core، ویژگی جدیدی است به نام ResponseCache و هدف آن تنظیم هدرهای مرتبط با caching مخصوص HTTP Response ارائه شدهاست. به همین جهت با مکانیزم OutputCache قدیمی ASP.NET MVC که اطلاعات را در حافظهی سرور کش میکرد، کاملا متفاوت است.
البته قرار است میان افزار OutputCache را در نگارشهای آتی ASP.NET Core نیز ارائه کنند.
[ResponseCache(Duration = 60)] public IActionResult Contact() { ViewData["Message"] = "Your contact page."; return View(); }
Cache-Control: public,max-age=60
یک نکته: این ویژگی را هم میتوان به کل کنترلر اعمال کرد و هم به یک اکشن متد خاص. اگر این ویژگی هم به کنترلر و هم به اکشن متدی در آن کنترلر اعمال شده باشد، تنظیمات در سطح متدها، تنظیمات در سطح کلاس را بازنویسی میکنند.
تعیین مکان کش شدن خروجی یک اکشن متد
در هدر فوق، عبارت public را مشاهده میکنید. این public بودن به این معنا است که امکان کش شدن این خروجی، توسط کش سرورهای اشتراکی بین راه هم وجود دارد.
اگر میخواهید این امکان را غیرفعال کنید، نیاز است این public به private تنظیم شود:
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
غیرفعال کردن کش شدن خروجی یک اکشن متد
اگر خواستید از کش شدن خروجی یک اکشن متد تحت هر حالتی جلوگیری کنید، مکان آنرا به None و NoStore آنرا به true تنظیم کنید:
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(); }
Cache-Control: no-store,no-cache Pragma: no-cache
امکان تعریف پروفایلهای کش
بجای اینکه تنظیمات کش کردن تکراری را به انواع و اقسام اکشن متدها اعمال کنیم، میتوان برای آنها پروفایل ایجاد کرده و از نام این پروفایل، جهت به اشتراک گذاری تنظیمات استفاده کنیم. برای این منظور به کلاس آغازین برنامه مراجعه کرده و جایی که سرویس ASP.NET MVC را فعال سازی کردهاید، پروفایل کش جدیدی را تعریف کنید:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.CacheProfiles.Add("PrivateCache", new CacheProfile { Duration = 60, Location = ResponseCacheLocation.Client }); });
[ResponseCache(CacheProfileName = "PrivateCache")]
معرفی سرویس کش درون حافظهای
در نگارشهای پیشین ASP.NET، متدهایی برای کش کردن موقتی اطلاعات در حافظه و سپس بازیابی آنها وجود داشتند. در ASP.NET Core، این متدها توسط سرویس ارائه کنندهی IMemoryCache در اختیار برنامه قرار میگیرند. برای فعال سازی این سرویس جدید باید مراحل ذیل طی شوند:
الف) ابتدا بستهی Microsoft.Extensions.Caching.Memory را به لیست وابستگیهای پروژه در فایل project.json اضافه کنید:
{ "dependencies": { //same as before "Microsoft.Extensions.Caching.Memory": "1.0.0" },
public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache();
[Route("DNT/[controller]")] public class AboutController : Controller { private readonly IMemoryCache _memoryCache; public AboutController(IMemoryCache memoryCache) { _memoryCache = memoryCache; } [Route("")] public ActionResult Hello() { string cacheKey = "my-cache-key"; string greeting; if (!_memoryCache.TryGetValue(cacheKey, out greeting)) { greeting = "Hello"; // store in the cache _memoryCache.Set(cacheKey, greeting, new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromMinutes(1))); } return Content($"{greeting} from DNT!"); }
تنظیمات منقضی شدن کش نیز به حالت absolute تنظیم شدهاست. یعنی پس از یک دقیقه حتما منقضی میشود. اگر فراخوانیهای این متد زیاد است، میتوان حالت منقضی شدن sliding را تنظیم کرد:
new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromMinutes(5))
اگر خواستیم تا این کش سر ساعت منقضی شود، اما در طی این یک ساعت به صورت sliding عمل کند، میتوان از ترکیب دو حالت مطلق و لغزشی استفاده کرد:
new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromMinutes(5)) .SetAbsoluteExpiration(TimeSpan.FromHours(1))
یک نکته: اگر فشار حافظهی سرور زیاد شود، مدیر حافظهی این کش، شروع به منقضی کردن آیتمهایی با حق تقدم پایین میکند. بالاترین حق تقدم را حالت NeverRemove ذیل دارد:
new MemoryCacheEntryOptions() .SetPriority(CacheItemPriority.NeverRemove))
معرفی Tag Helpers مخصوص کش کردن قسمتی از صفحه
در ادامهی مبحث معرفی Tag Helpers، تعدادی از آنها جهت کش کردن محتوای قسمتی از صفحه، طراحی شدهاند:
<cache expires-after="@TimeSpan.FromMinutes(10)"> @Html.Partial("_WhatsNew") *last updated @DateTime.Now.ToLongTimeString() </cache>
برای نمونه در مثال فوق، محتوای پارشال ویوو رندر شده و همچنین تاریخی که پس از آن نمایش داده شدهاست، به مدت 10 دقیقه در حافظهی سرور کش میشوند. اگر این زمان تنظیم نشود، تا زمانیکه برنامه در سرور مشغول به کار است، این قسمت منقضی نخواهد شد.
در اینجا اگر expires-after ذکر شده بود، یعنی پس از این مدت زمان، کش منقضی میشود.
<cache expires-after="@TimeSpan.FromSeconds(5)"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache expires-on="@DateTime.Today.AddDays(1).AddTicks(-1)"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache expires-sliding="@TimeSpan.FromMinutes(5)"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache vary-by-user="true"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache vary-by-route="id"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache vary-by-query="search"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache vary-by-cookie="MyAppCookie"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache vary-by-header="User-Agent"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache vary-by="@ViewBag.ProductId"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
<cache vary-by-user="true" vary-by-route="id"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
به علاوه چون زیر ساخت این Tag Helper همان Microsoft.Extensions.Caching.Memory است، امکان تنظیم حق تقدم حذف شدن آیتمهای کش شده نیز وجود دارد:
<cache expires-sliding="@TimeSpan.FromMinutes(10)" priority="@Microsoft.Extensions.Caching.Memory.CacheItemPriority.NeverRemove"> <!--View Component or something that gets data from the database--> *last updated @DateTime.Now.ToLongTimeString() </cache>
مبحث تکمیلی
امکان ذخیره سازی آیتمهای کش شده در بانک اطلاعاتی (بجای حافظهی فرار) نیز پیش بینی شدهاست که تحت عنوان «کش توزیع شده» در دسترس است.
Working with a Distributed Cache
پیشتر در مورد ELMAH مطلبی را منتشر کرده بودم و اگر برنامه نویس ASP.NET هستید و با ELMAH آشنایی ندارید، جدا نیمی از عمر کاری شما بر فنا است!
هاست پیش فرض یک WCF RIA Service هم یک برنامهی ASP.NET است. بنابراین کلیهی خطاهای رخ داده در سمت سرور را باید بتوان به نحوی لاگ کرد تا بعدا با مطالعهی آنها اطلاعات ارزشمندی را از نقایص برنامه در عمل و پیش از گوشزد شدن آنها توسط کاربران، دریافت، بررسی و رفع کرد.
کلیه خطاها را لاگ میکنم تا:
- بدانم معنای جملهی "برنامه کار نمیکنه" چی هست.
- بدون روبرو شدن با کاربران یا حتی سؤال و جوابی از آنها بدانم دقیقا مشکل از کجا ناشی شده.
- بدانم رفتارهای عمومی کاربران که منجر به بروز خطا میشوند کدامها هستند.
- بدانم در کدامیک از قسمتهای برنامه تعیین اعتبار ورودی کاربران یا انجام نشده یا ضعیف و ناکافی است.
- بدانم زمانیکه دوستی (!) قصد پایین آوردن برنامه را با تزریق SQL داشته، دقیقا چه چیزی را وارد کرده، در کجا و چه زمانی؟
- بتوانم Remote worker خوبی باشم.
ELMAH هم برای لاگ کردن خطاهای مدیریت نشدهی یک برنامهی ASP.NET ایجاد شده است. بنابراین باید بتوان این دو (WCF RIA Services و ELMAH) را به نحوی با هم سازگار کرد. برای اینکار نیاز است تا یک مدیریت کنندهی خطای سفارشی را با پیاده سازی اینترفیس IErrorHandler تهیه کنیم (تا خطاهای مدیریت نشدهی حاصل را به سمت ELMAH هدایت کند) و سپس آنرا به کمک یک ویژگی یا Attribute به DomainService خود جهت لاگ کردن خطاها اعمال نمائیم. روش تعریف این Attribute را در کدهای بعد ملاحظه خواهید نمود (در اینجا نیاز است تا دو ارجاع را به اسمبلیهای Elmah.dll که دریافت کردهاید و اسمبلی استاندارد System.ServiceModel نیز به پروژه اضافه نمائید):
//add a reference to "Elmah.dll"
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Web;
namespace ElmahWcf
{
public class HttpErrorHandler : IErrorHandler
{
#region IErrorHandler Members
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error == null)
return;
if (HttpContext.Current == null) //In case we run outside of IIS
return;
Elmah.ErrorSignal.FromCurrentContext().Raise(error);
}
#endregion
}
}
//add a ref to "System.ServiceModel" assembly
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace ElmahWcf
{
public class ServiceErrorBehaviorAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;
public ServiceErrorBehaviorAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}
#region IServiceBehavior Members
public void AddBindingParameters(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{ }
public void ApplyDispatchBehavior(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
cd.ErrorHandlers.Add(errorHandler);
}
}
public void Validate(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{ }
#endregion
}
}
[ServiceErrorBehavior(typeof(HttpErrorHandler))] //Integrating with ELMAH
[EnableClientAccess()]
public partial class MyDomainService : LinqToEntitiesDomainService<myEntities>
در ادامه نحوهی افزودن تعاریف متناظر با ELMAH به Web.Config برنامه ذکر شده است. این تعاریف برای IIS6 و 7 به بعد هم تکمیل گردیده است. خطاها هم به صورت فایلهای XML در پوشهای به نام Errors که به ریشهی سایت اضافه خواهید نمود (یا هر پوشهی دلخواه دیگری)، لاگ میشوند.
به نظر من این روش، از ذخیره سازی اطلاعات لاگها در دیتابیس بهتر است. چون اساسا زمانیکه خطایی رخ میدهد شاید مشکل اصلی همان ارتباط با دیتابیس باشد.
قسمت ارسال خطاها به صورت ایمیل نیز comment شده است که در صورت نیاز میتوان آنرا فعال نمود:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>
<elmah>
<security allowRemoteAccess="1" />
<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/Errors" />
<!-- <errorMail
from="errors@site.net"
to="nasiri@site.net"
subject="prj-error"
async="true"
smtpPort="25"
smtpServer="mail.site.net"
noYsod="true" /> -->
</elmah>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="DomainServiceModule"
preCondition="managedHandler"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add name="Elmah" verb="POST,GET,HEAD" path="myelmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</handlers>
</system.webServer>
<system.web>
<globalization
requestEncoding="utf-8"
responseEncoding="utf-8"
/>
<authentication mode="Forms">
<!--one month ticket-->
<forms name=".403AuthV"
cookieless="UseCookies"
slidingExpiration="true"
protection="All"
path="/"
timeout="43200" />
</authentication>
<httpHandlers>
<add verb="POST,GET,HEAD" path="myelmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="DomainServiceModule"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</httpModules>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</assemblies>
</compilation>
</system.web>
<connectionStrings>
</connectionStrings>
<system.serviceModel>
<serviceHostingEnvironment
aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
throw new Exception("This is an ELMAH test");
سپس به آدرس http://localhost/myelmah.axd مراجعه نموده و اطلاعات لاگ شده حاصل را بررسی کنید:
این روش با WCF Services های متداول هم کار میکند. فقط در این سرویسها باید aspNetCompatibilityEnabled مطابق تگهای ذکر شدهی system.serviceModel فوق در web.config لحاظ شوند (این مورد به صورت پیش فرض در WCF RIA Services وجود دارد). همچنین ویژگی زیر نیز باید به سرویس شما اضافه گردد:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
منابع مورد استفاده:
Integrating ELMAH for a WCF Service
Making WCF and ELMAH play nice together
Getting ELMAH to work with WCF services
پ.ن.
اگر به خطاهای ASP.NET دقت کرده باشید که به yellow screen of death هم مشهور هستند (در مقابل صفحات آبی ویندوز!)، ابتدای آن خیلی بزرگ نوشته شده Server Error و سپس ادامهی خطا. همین مورد دقیقا یادم هست که هر بار سبب بازخواست مدیران شبکه بجای برنامه نویسها میشد! (احتمالا این هم یک نوع بدجنسی تیم ASP.NET برای گرفتن حال ادمینهای شبکه است! و گرنه مثلا میتوانستند همان ابتدا بنویسند program/application error بجای server error)
یک نکتهی تکمیلی: خواص init-only در زمان اجرای برنامه read-only نیستند.
تمام مواردی که در مطلب جاری بحث شدند، مرتبط با زمان کامپایل هستند. در زمان اجرای برنامه و با استفاده از reflection، میتوان مقادیر init-only را همانند سایر خواص ;get; set دار، تنظیم کرد:
PropertyInfo propertyInfo = typeof(Person).GetProperty(nameof(User.Name)); propertyInfo.SetValue(user, "edited"); Console.WriteLine(user.Name); // Print "edited"
همچنین اینگونه خواص را توسط reflection بر اساس ویژگی IsExternalInit آنها میتوان تشخیص داد:
public static bool IsInitOnly(this PropertyInfo propertyInfo) { MethodInfo setMethod = propertyInfo.SetMethod; if (setMethod == null) return false; var isExternalInitType = typeof(System.Runtime.CompilerServices.IsExternalInit); return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(isExternalInitType); }
PropertyInfo propertyInfo = typeof(Person).GetProperty(nameof(Person.Name)); var isInitOnly = propertyInfo.IsInitOnly();
چند نکتهی تکمیلی
- با ارائهی نگارش RC، مداخل ذکر شدهی در صفحهی index.html کاهش یافته و به فایل systemjs.config.js منتقل شدهاند.
- با استفاده از فایل systemjs.config.js دیگر نیازی به ذکر متد ({}) System.config در فایل index.html نیست.
- تعاریف فایل main.ts اینبار از مسیر ذیل خوانده میشوند:
- دیگر نیازی به ذکر typings/browser.d.ts نیست. همینقدر که فایل typings.json را به همراه تنظیم ذیل در فایل Package.json داشته باشید، مشکلی برای کامپایل فایلها و مداخل مرتبط با ES 6 نخواهید داشت.
- با ارائهی نگارش RC، مداخل ذکر شدهی در صفحهی index.html کاهش یافته و به فایل systemjs.config.js منتقل شدهاند.
- با استفاده از فایل systemjs.config.js دیگر نیازی به ذکر متد ({}) System.config در فایل index.html نیست.
- تعاریف فایل main.ts اینبار از مسیر ذیل خوانده میشوند:
/// <reference path="../typings/es6-shim.d.ts" /> import {bootstrap} from '@angular/platform-browser-dynamic';
"scripts": { "postinstall": "typings install" },