من همین روش رو برای گریدویو تودرتو که داخل یه div هستند و آن div بصورت runat=server میباشد و آن را با استفاده از متد RenderControl داخل یه String Writer ریختم و اونو بعنوان پارامتر StreamReader پاس دادم.نتیجه این شد که فیلدهای گرید بیرونی درست نمایش داده شدند ولی گریدویو داخلی متنهای آن بصورت برعکس و نچسبیده نمایان شدندممنون یشم راهنمایی کنید.
نظرات مطالب
Senior Developer به چه کسی گفته می شود؟
اصولا بهترین روش برای تشخیص اینکه شما در کدام سطح قرار دارید این است که خودتون به مسیری که طی کردید تا به جایگاه فعلی برسید یک نگاه دقیق داشته باشید. اصولا اکثر برنامه نویسانی که در شرکتهای نرم افزاری مشغول به کار هستند در سطح Junior یا Mid Level هستند. در این پست تمرکز اصلی برای تشریح سطح برنامه نویس ارشد بود. متاسفانه در اکثر آگهیهای استخدام شاهد این هستیم که برنامه نویس باید با انبوهی از تکنولوژیهای آشنایی داشته باشد. مثلا حتما در آگهیها دیده اید که برنامه نویس Asp.Net MVC مسلط به JQuery و HTML و CSS و JavaScript و EF و.... بعنی باید برنامه نویس هم در لایههای سطح پایین نظیر Business و سرویس برنامه نویسی کند و هم به عنوان یک Front- End کار.
به صورت معمول زمانی که برنامه نویسان خودشون را در جایگاه Senior Developer نمیبینند از واژه Software Developer استفاده میکنند.
اشتراکها
راهنمای جامع سلکتور ها در CSS
در قسمت قبل، کار پیاده سازی سمت سرور نمایش اطلاعات یک گرید، به پایان رسید. در این قسمت میخواهیم از سمت کلاینت، اطلاعات صفحه بندی و مرتب سازی را به سمت سرور ارسال کرده و همچنین نتیجهی دریافتی از سرور را نمایش دهیم.
پیشنیازهای نمایش اطلاعات گرید به همراه صفحه بندی اطلاعات
در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانههای ثالث» نحوهی نصب و معرفی کتابخانهی ngx-bootstrap را بررسی کردیم. دقیقا همان مراحل، در اینجا نیز باید طی شوند و از این مجموعه تنها به کامپوننت Pagination آن نیاز داریم. همان قسمت ذیل گرید تصویر فوق که شماره صفحات را جهت انتخاب، نمایش دادهاست.
بنابراین ابتدا فرض بر این است که دو بستهی بوت استرپ و ngx-bootstrap را نصب کردهاید:
در فایل angular-cli.json. شیوهنامهی بوت استرپ را نیز افزودهاید:
پس از آن باید بهخاطر داشت که کامپوننت نمایش صفحه بندی این مجموعه PaginationModule نام دارد و باید در نزدیکترین ماژول مورد نیاز، ثبت و معرفی شود:
برای نمونه در این مثال، ماژولی به نام simple-grid.module.ts دربرگیرندهی گرید مطلب جاری است و به صورت ذیل به برنامه اضافه شدهاست:
بنابراین تعریف PaginationModule باید به قسمت imports این ماژول اضافه شود و تعریف آن در app.module.ts تاثیری بر روی این قسمت نخواهد داشت.
کامپوننتی هم که مثال جاری را نمایش میدهد به صورت ذیل به ماژول SimpleGrid فوق اضافه شدهاست:
تهیه معادلهای قراردادهای سمت سرور در سمت Angular
در قسمت قبل، تعدادی قرارداد مانند پارامترهای دریافتی از سمت کلاینت و ساختار اطلاعات ارسالی به سمت کلاینت را تعریف کردیم. اکنون جهت کار strongly typed با آنها در سمت یک برنامهی تایپ اسکریپتی Angular، کلاسهای معادل آنها را تهیه میکنیم.
ساختار شیء محصول دریافتی از سمت سرور
با این محتوا
که در اینجا هر کدام از خواص ذکر شده، معادل camel case نمونهی سمت سرور خود هستند (چون JSON.NET در ASP.NET Core، به صورت پیش فرض یک چنین خروجی را تولید میکند).
ساختار معادل پارامترهای صفحه بندی و مرتب سازی ارسالی به سمت سرور
با این محتوا
در اینجا همان ساختار IPagedQueryModel سمت سرور را مشاهده میکنید. از آن جهت مشخص سازی جزئیات صفحه بندی و نحوهی مرتب سازی اطلاعات، استفاده میشود.
ساختار معادل اطلاعات صفحه بندی شدهی دریافتی از سمت سرور
با این محتوا
این ساختار جنریک نیز دقیقا معادل همان PagedQueryResult سمت سرور است و حاوی تعداد کل ردیفهای یک کوئری و تنها قسمتی از اطلاعات صفحه بندی شدهی آن میباشد.
ساختار ستونهای گرید نمایشی
با این محتوا
هر ستون نمایش داده شده، دارای یک برچسب، خاصیتی مشخص در سمت سرور و بیانگر قابلیت مرتب سازی آن میباشد. اگر isSortable به true تنظیم شود، با کلیک بر روی سرستونها میتوان اطلاعات را بر اساس آن ستون، مرتب سازی کرد.
تهیه سرویس ارسال اطلاعات صفحه بندی به سرور و دریافت اطلاعات از آن
پس از تدارک این مقدمات، اکنون کار تعریف سرویسی که این اطلاعات را به سمت سرور ارسال میکند و نتیجه را باز میگرداند، به صورت ذیل خواهد بود:
این دستور سبب ایجاد کلاس ProductsListService شده و همچنین قسمت providers ماژول simple-grid را نیز بر این اساس به روز رسانی میکند.
پیش از تکمیل این سرویس، نیاز است متدی را جهت تبدیل یک شیء، به معادل کوئری استرینگ آن تهیه کنیم:
در قسمت قبل امضای متد GetPagedProducts دارای ویژگی HttpGet است. بنابراین، نیاز است اطلاعات را به صورت کوئری استرینگ از سمت کلاینت دریافت کند و متد toQueryString فوق به صورت خودکار بر روی تمام خواص یک شیء دلخواه حرکت کرده و آنها را تبدیل به یک رشتهی حاوی کوئری استرینگها میکند.
برای نمونه متد toQueryString فوق است که سبب ارسال یک چنین درخواستی به سمت سرور میشود:
پس از این تعریف، سرویس ProductsListService به صورت ذیل تکمیل خواهد شد:
در اینجا از متد toQueryString، جهت تکمیل متد get ارسالی به سمت سرور استفاده شدهاست تا پارامترها را به صورت کوئری استرینگها تبدیل کرده و ارسال کند.
سپس در متد map آن، res.json دقیقا همان ساختار PagedQueryResult سمت سرور را به همراه دارد. اینجا است که فرصت خواهیم داشت نمونهی سمت کلاینت آنرا که در ابتدای بحث تهیه کردیم، وهله سازی کرده و بازگشت دهیم (نگاشت فیلدهای دریافتی از سمت سرور به سمت کلاینت).
تکمیل کامپوننت نمایش گرید
قسمت آخر این مطلب، استفادهی از این ساختارها و سرویسها و نمایش اطلاعات دریافتی از آنها است. برای این منظور ابتدا نیاز است سرستونهای این گرید را تهیه کرد:
در اینجا ابتدا بررسی میشود که آیا یک ستون قابلیت مرتب سازی را دارد، یا خیر؟ اگر اینطور است، در کنار آن یک گلیف آیکن مرتب سازی درج میشود. اگر خیر، صرفا متن عنوان آن نمایش داده خواهد شد. میشد تمام این موارد را به ازای هر ستون به صورت مجزایی ارائه داد، اما در این حالت به کدهای تکراری زیادی میرسیدیم. به همین جهت از یک حلقه بر روی تعریف ستونهای این گرید استفاده شدهاست. آرایهی این ستونها نیز به صورت ذیل تعریف میشود:
همچنین در کدهای قالب این کامپوننت، مدیریت کلیک بر روی یک سر ستون را نیز مشاهده میکنید:
در اینحالت اگر ستونی که بر روی آن کلیک شده، پیشتر مرتب سازی شدهاست، صرفا خاصیت صعودی بودن آن برعکس خواهد شد. در غیراینصورت، نام خاصیت درخواستی مرتب سازی و جهت آن نیز مشخص میشود. سپس مجددا این گرید توسط متد getPagedProductsList رندر خواهد شد.
کار رندر بدنهی اصلی گرید توسط همین چند سطر در قالب آن مدیریت میشود:
اولین ستون آن، اندکی ابتکاری است. در اینجا شماره ردیفهای خودکاری در هر صفحه درج خواهند شد. این شماره ردیف نیز جزو ستونهای منبع دادهی فرضی برنامه نیست. به همین جهت برای درج آن، توسط let i = index در ngFor، به شماره ایندکس ردیف جاری دسترسی پیدا میکنیم. سپس توسط محاسباتی بر اساس تعداد ردیفهای هر صفحه و شمارهی صفحهی جاری، میتوان شماره ردیف فعلی را محاسبه کرد.
در اینجا حلقهای بر روی queryResult.items تشکیل شدهاست. این منبع داده به صورت ذیل در کامپوننت متناظر مقدار دهی میشود:
ابتدا سرویس ProductsListService را که در ابتدای بحث تکمیل شد، به سازندهی این کامپوننت تزریق میکنیم. به کمک آن میتوان در متد getPagedProductsList، ابتدا queryModel جاری را که شامل اطلاعات مرتب سازی و صفحه بندی است، به سرور ارسال کرده و سپس نتیجهی نهایی را به queryResult انتساب دهیم. به این ترتیب تعداد کل رکوردها و همچنین آیتمهای صفحهی جاری دریافت میشوند. اکنون حلقهی ngFor نمایش بدنهی گرید، کار تکمیل صفحهی جاری را انجام خواهد داد.
قسمت آخر کار، افزودن کامپوننت نمایش شماره صفحات است:
در اینجا از کامپوننت pagination مجموعهی ngx-bootstarp استفاده شدهاست و یک سری از خواص مستند شدهی آن، مقدار دهی شدهاند؛ مانند متنهای صفحهی بعد و قبل و امثال آن. مدیریت کلیک بر روی شمارههای آن، در کامپوننت جاری به صورت ذیل است:
علت تعریف دو خاصیت اضافهی currentPage و numberOfPages، استفادهی از آنها در قسمت ذیل این شمارهها (خارج از کامپوننت نمایش شماره صفحات) جهت نمایش page 1/x است.
هر زمانیکه کاربر بر روی شمارهای کلیک میکند، رخداد onPageChange فراخوانی شده و در اینحالت تنها کافی است شماره صفحهی درخواستی queryModel جاری را به روزرسانی کرده و سپس آنرا در اختیار متد getPagedProductsList جهت دریافت اطلاعات این صفحهی درخواستی قرار دهیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
پیشنیازهای نمایش اطلاعات گرید به همراه صفحه بندی اطلاعات
در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانههای ثالث» نحوهی نصب و معرفی کتابخانهی ngx-bootstrap را بررسی کردیم. دقیقا همان مراحل، در اینجا نیز باید طی شوند و از این مجموعه تنها به کامپوننت Pagination آن نیاز داریم. همان قسمت ذیل گرید تصویر فوق که شماره صفحات را جهت انتخاب، نمایش دادهاست.
بنابراین ابتدا فرض بر این است که دو بستهی بوت استرپ و ngx-bootstrap را نصب کردهاید:
> npm install bootstrap --save > npm install ngx-bootstrap --save
"apps": [ { "styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ],
import { PaginationModule } from "ngx-bootstrap"; @NgModule({ imports: [ PaginationModule.forRoot() ]
>ng g m SimpleGrid -m app.module --routing
کامپوننتی هم که مثال جاری را نمایش میدهد به صورت ذیل به ماژول SimpleGrid فوق اضافه شدهاست:
>ng g c SimpleGrid/products-list
تهیه معادلهای قراردادهای سمت سرور در سمت Angular
در قسمت قبل، تعدادی قرارداد مانند پارامترهای دریافتی از سمت کلاینت و ساختار اطلاعات ارسالی به سمت کلاینت را تعریف کردیم. اکنون جهت کار strongly typed با آنها در سمت یک برنامهی تایپ اسکریپتی Angular، کلاسهای معادل آنها را تهیه میکنیم.
ساختار شیء محصول دریافتی از سمت سرور
>ng g cl SimpleGrid/app-product
export class AppProduct { constructor( public productId: number, public productName: string, public price: number, public isAvailable: boolean ) {} }
ساختار معادل پارامترهای صفحه بندی و مرتب سازی ارسالی به سمت سرور
>ng g cl SimpleGrid/PagedQueryModel
export class PagedQueryModel { constructor( public sortBy: string, public isAscending: boolean, public page: number, public pageSize: number ) {} }
ساختار معادل اطلاعات صفحه بندی شدهی دریافتی از سمت سرور
>ng g cl SimpleGrid/PagedQueryResult
export class PagedQueryResult<T> { constructor(public totalItems: number, public items: T[]) {} }
ساختار ستونهای گرید نمایشی
>ng g cl SimpleGrid/GridColumn
export class GridColumn { constructor( public title: string, public propertyName: string, public isSortable: boolean ) {} }
تهیه سرویس ارسال اطلاعات صفحه بندی به سرور و دریافت اطلاعات از آن
پس از تدارک این مقدمات، اکنون کار تعریف سرویسی که این اطلاعات را به سمت سرور ارسال میکند و نتیجه را باز میگرداند، به صورت ذیل خواهد بود:
>ng g s SimpleGrid/products-list -m simple-grid.module
پیش از تکمیل این سرویس، نیاز است متدی را جهت تبدیل یک شیء، به معادل کوئری استرینگ آن تهیه کنیم:
toQueryString(obj: any): string { const parts = []; for (const key in obj) { if (obj.hasOwnProperty(key)) { const value = obj[key]; if (value !== null && value !== undefined) { parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); } } } return parts.join("&"); }
[HttpGet("[action]")] public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7
پس از این تعریف، سرویس ProductsListService به صورت ذیل تکمیل خواهد شد:
@Injectable() export class ProductsListService { private baseUrl = "api/Product"; constructor(private http: Http) {} getPagedProductsList( queryModel: PagedQueryModel ): Observable<PagedQueryResult<AppProduct>> { return this.http .get(`${this.baseUrl}/GetPagedProducts?${this.toQueryString(queryModel)}`) .map(res => { const result = res.json(); return new PagedQueryResult<AppProduct>( result.totalItems, result.items ); }); }
سپس در متد map آن، res.json دقیقا همان ساختار PagedQueryResult سمت سرور را به همراه دارد. اینجا است که فرصت خواهیم داشت نمونهی سمت کلاینت آنرا که در ابتدای بحث تهیه کردیم، وهله سازی کرده و بازگشت دهیم (نگاشت فیلدهای دریافتی از سمت سرور به سمت کلاینت).
تکمیل کامپوننت نمایش گرید
قسمت آخر این مطلب، استفادهی از این ساختارها و سرویسها و نمایش اطلاعات دریافتی از آنها است. برای این منظور ابتدا نیاز است سرستونهای این گرید را تهیه کرد:
<table class="table table-striped table-hover table-bordered table-condensed"> <thead> <tr> <th class="text-center" style="width:3%">#</th> <th *ngFor="let column of columns" class="text-center"> <div *ngIf="column.isSortable" (click)="sortBy(column.propertyName)" style="cursor: pointer"> {{ column.title }} <i *ngIf="queryModel.sortBy === column.propertyName" class="glyphicon" [class.glyphicon-sort-by-order]="queryModel.isAscending" [class.glyphicon-sort-by-order-alt]="!queryModel.isAscending"></i> </div> <div *ngIf="!column.isSortable" style="cursor: pointer"> {{ column.title }} </div> </th> </tr> </thead>
export class ProductsListComponent implements OnInit { columns: GridColumn[] = [ new GridColumn("Id", "productId", true), new GridColumn("Name", "productName", true), new GridColumn("Price", "price", true), new GridColumn("Available", "isAvailable", true) ];
همچنین در کدهای قالب این کامپوننت، مدیریت کلیک بر روی یک سر ستون را نیز مشاهده میکنید:
export class ProductsListComponent implements OnInit { itemsPerPage = 7; queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage); sortBy(columnName) { if (this.queryModel.sortBy === columnName) { this.queryModel.isAscending = !this.queryModel.isAscending; } else { this.queryModel.sortBy = columnName; this.queryModel.isAscending = true; } this.getPagedProductsList(); } }
کار رندر بدنهی اصلی گرید توسط همین چند سطر در قالب آن مدیریت میشود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> </tr> </tbody> </table>
در اینجا حلقهای بر روی queryResult.items تشکیل شدهاست. این منبع داده به صورت ذیل در کامپوننت متناظر مقدار دهی میشود:
export class ProductsListComponent implements OnInit { itemsPerPage = 7; currentPage: number; numberOfPages: number; isLoading = false; queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage); queryResult = new PagedQueryResult<AppProduct>(0, []); constructor(private productsListService: ProductsListService) {} ngOnInit() { this.getPagedProductsList(); } private getPagedProductsList() { this.isLoading = true; this.productsListService .getPagedProductsList(this.queryModel) .subscribe(result => { this.queryResult = result; this.isLoading = false; }); } }
قسمت آخر کار، افزودن کامپوننت نمایش شماره صفحات است:
<div align="center"> <pagination [maxSize]="8" [boundaryLinks]="true" [totalItems]="queryResult.totalItems" [rotate]="false" previousText="‹" nextText="›" firstText="«" lastText="»" (numPages)="numberOfPages = $event" [(ngModel)]="currentPage" (pageChanged)="onPageChange($event)"></pagination> </div> <pre class="card card-block card-header">Page: {{currentPage}} / {{numberOfPages}}</pre>
export class ProductsListComponent implements OnInit { itemsPerPage = 7; currentPage: number; numberOfPages: number; onPageChange(event: any) { this.queryModel.page = event.page; this.getPagedProductsList(); } }
هر زمانیکه کاربر بر روی شمارهای کلیک میکند، رخداد onPageChange فراخوانی شده و در اینحالت تنها کافی است شماره صفحهی درخواستی queryModel جاری را به روزرسانی کرده و سپس آنرا در اختیار متد getPagedProductsList جهت دریافت اطلاعات این صفحهی درخواستی قرار دهیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
کلمهی LINQ مخفف Language Integrated Query یا زبان پرس و جوی یکپارچه میباشد. LINQ برای اولین بار در ویژوال استودیوی 2008 و دات نت فریم ورک 3.5 برای پرکردن خلع بین دنیای اشیاء برنامه نویسی (Object Oriented World) و دنیای دادهها (Data World) ارائه شد.
چرا LINQ؟
در نگاهی کلی، مزایایی که از طریق LINQ حاصل میشوند عبارتند از:
• کاهش حجم کدنویسی
• درک بهتر از عملکرد کدهای نوشته شده
• پس از یادگیری اصول LINQ به راحتی میتوانید از این اصول پرس و جو نویسی برای کار بر روی مجموعه دادههای مختلف استفاده کنید
• کنترل صحت کدهای پرس و جوها در زمان کامپایل ( Compile-Time Type Checking )
اجزای سازندهی LINQ
دو جزء اصلی سازندهی LINQ عبارت است از:
- Elements عناصر
- Sequences توالیها
توالیها میتوانند لیستی از اطلاعات مختلف باشند. هر آیتم در لیست را عنصر میگوییم. توالی نمونهای از یک کلاس است که اینترفیس <IEnumarable<T را پیاده سازی کرده باشد. این اینترفیس تضمین میکند که توالی قابلیت پیمایش عناصر را دارد.
به آرایهی تعریف شدهی زیر دقت کنید:
int[] fibonacci = {0, 1, 1, 2, 3, 5};
متغیر fibonacci در اینجا نشان دهندهی توالی و هر یک از اعداد آرایه، یک عنصر محسوب میشوند.
توالی میتواند درون حافظهای باشد (In Memory Object) که به آن Local Sequence میگویند و یا میتواند یک بانک اطلاعاتی SQL Server باشد که به آن Remote Sequence میگویند.
در حالت Remote باید اینترفیس <IQuerable<T پیاده سازی شده باشد.
پرس و جو هایی را که بر روی توالیهای محلی اجرا میشوند، اصطلاحا Local Query و یا LINQ-To-Object نیز مینامند.
عملگرهای پرس و جوی زیادی به شکل متد الحاقی در کلاس System.Linq.Enumerable طراحی شدهاند. این مجموعه از عملگرهای پرس جو را اصطلاحا Standard Query Operator میگویند.
نکتهی مهم این است که عملگرهای پرس و جو تغییری را در توالی ورودی نمیدهند و نتیجهی خروجی یک مجموعه جدید و یا یک مقدار عددی میباشد.
توالی خروجی و مقدار بازگشتی Scalar
در بخش قبل گفتیم که خروجی یک پرس و جو میتواند یک مجموعه و یا یک مقدار عددی باشد. در مثال زیر عملگر Count را بر روی مجموعهی fibonacci اعمال کردیم و عددی که نشان دهندهی تعداد عناصر مجموعه است، بعنوان خروجی بازگردانده شده است.
int[] fibonacci = { 0, 1, 1, 2, 3, 5 }; int numberOfElements = fibonacci.Count(); Console.WriteLine($"{numberOfElements}"); IEnumerable<int> distinictNumbers = fibonacci.Distinct(); Console.WriteLine("Elements in output sequence:"); foreach (var number in distinictNumbers) { Console.WriteLine(number); }
در کد بالا توسط تابع Distinct، عناصر یکتا را از توالی ورودی استخراج کرده و بازگرداندهایم.
خروجی برنامهی فوق به شکل زیر است :
6 Elements in output sequence: 0 1 2 3 5
مفهوم Deffer Execution ( اجرای به تاخیر افتاده )
عمدهی عملگرهای پرس و جو بلافاصله پس از ایجاد، اجرا نمیشوند. این عملگرها در طول اجرای برنامه اجرا خواهند شد (اجرای با تاخیر). به همین خاطر میتوان بعد از ساخت پرس و جو تغییرات دلخواهی را به توالی ورودی اعمال کرد.
در کد زیر قبل از اجرای پرس و جو ، توالی ورودی ویرایش شده :
int[] fibonacci = { 0, 1, 1, 2, 3, 5 }; // ایجاد پرس و جو IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2); // در این مرحله پرس و جو ایجاد شده ولی هنوز اجرا نشده است // تغییر عنصر اول توالی fibonacci[0] = 99; // حرکت بر روی عناصر توالی باعث اجرای پرس و جو میشود foreach (var number in numbersGreaterThanTwoQuery) { Console.WriteLine(number); }
پرس و جو تا زمان اجرای حلقهی Foreach اجرا نخواهد شد. خروجی مثال بالا به شکل زیر است :
99 3 5
به غیر از بعضی از عملگرها مثل Count,Min,Last سایر عملگرها بصورت اجرای با تاخیر عمل میکنند. عملگری مثل Count باعث اجرای فوری پرس و جو میشود.
تعدادی عملگر تبدیل (Conversion Operator) هم وجود دارد که باعث میشوند پرس و جو بلافاصله اجرا شود :
• ToList
• ToArray
• ToLookup
• ToDictionary
عملگرهای فوق پس از اجرا، خروجی را در یک ساختمان دادهی جدید باز میگردانند.
در کد زیر اصلاح توالی متغیر Fibonacci بعد از اجرای تابع ToArray صورت گرفته است.
int[] fibonacci = { 0, 1, 1, 2, 3, 5 }; // ساخت پرس و جو IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2) .ToArray(); // در این مرحله به خاطر عملگر استفاده شده پرس و جو اجرا میشود // تغییر اولین عنصر توالی fibonacci[0] = 99; // حرکت بر روی نتیجه foreach (var number in numbersGreaterThanTwoQuery) { Console.WriteLine(number); }
خروجی مثال بالا:
3 5
همانطور که میبینید عدد 99 در خروجی مشاهده نمیشود. علت فراخوانی عملگر ToArray است که بلافاصله باعث اجرای پرس و جو شده و خروجی را باز میگرداند . به همین خاطر تغییر عنصر اول توالی ورودی، تاثیری بر روی نتیجهی خروجی ندارد.
زمانی که وظیفه ای میخواهد اجرا شود همزمان با آن کنترلر Home هم فراخوانی میشود
این درخواست هایی هست که لاگ VS قسمت Output ثبت میشود
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Dependency duration","kind":"Aggregation","value":4143.777,"count":1,"min":4143.777,"max":4143.777,"stdDev":0}],"properties":{"_MS.MetricId":"dependencies/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","Dependency.Type":"Http","DeveloperMode":"true","Dependency.Success":"True"}}}} Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Server response time","kind":"Aggregation","value":4136.7653,"count":1,"min":4136.7653,"max":4136.7653,"stdDev":0}],"properties":{"_MS.MetricId":"requests/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","DeveloperMode":"true","Request.Success":"True"}}}} Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Dependency duration","kind":"Aggregation","value":3972.6929,"count":3,"min":12.8297,"max":2687.0447,"stdDev":1092.34881420812}],"properties":{"_MS.MetricId":"dependencies/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","Dependency.Type":"SQL","DeveloperMode":"true","Dependency.Success":"True"}}}}
#region Using using Autofac; using CHK.ServiceLayer.Interfaces; using DNTScheduler; using System; #endregion namespace CHK.Web.Scheduler { public class InventoryTask : ScheduledTaskTemplate { #region Properties public override int Order => 1; public override string Name => "Inventory-DiscountCoupon-GiftCard"; public IContainer Container { get; set; } #endregion #region Methods public override bool RunAt(DateTime utcNow) { if (IsShuttingDown || Pause) return false; var currentDateTime = utcNow.AddHours(3.5); return (currentDateTime.Minute % 15 == 0 && currentDateTime.Second > 5 && currentDateTime.Second < 15); } public override void Run() { if (IsShuttingDown || Pause) return; Pause = true; using (var scope = Container.BeginLifetimeScope()) { var schedulerService = scope.Resolve<ISchedulerService>(); schedulerService.UpdateInventory(); } Pause = false; } #endregion } }
فضای نام System.Net.Mail در NET Core 1.2. که پیاده سازی netstandard2.0 است، ارائه خواهد شد. بنابراین فعلا (در زمان NET Core 1.1.) راه حل توکار و رسمی برای ارسال ایمیل در برنامههای مبتنی بر NET Core. وجود ندارد. اما میتوان کتابخانهی ثالثی را به نام MailKit، به عنوان راهحلی که .NET 4.0, .NET 4.5, .NET Core, Xamarin.Android, و Xamarin.iOS را پشتیبانی میکند، درنظر گرفت و توانمندیها و پروتکلهای پشتیبانی شدهی توسط آن، از System.Net.Mail توکار نیز بسیار بیشتر است.
افزودن وابستگیهای MailKit به برنامه
برای شروع به استفادهی از MailKit، میتوان بستهی نیوگت آنرا به فایل project.json برنامه معرفی کرد:
استفاده از MailKit جهت تکمیل وابستگیهای ASP.NET Core Identity
قسمتی از ASP.NET Core Identity، شامل ارسال ایمیلهای «ایمیل خود را تائید کنید» است که آنرا میتوان توسط MailKit به نحو ذیل تکمیل کرد:
در اینجا MimeMessage بیانگر محتوا و تنظیمات ایمیلی است که قرار است ارسال شود. ابتدا قسمتهای From و To آن تنظیم میشوند تا مشخص باشد که ایمیل ارسالی از کجا ارسال شده و قرار است به چه آدرسی ارسال شود. در ادامه موضوع و عنوان ایمیل تنظیم شدهاست. سپس متن ایمیل، به خاصیت Body شیء MimeMessage انتساب داده خواهد شد. فرمت ایمیل ارسالی را نیز میتوان در اینجا تنظیم کرد. برای مثال TextFormat.Html جهت ارسال پیامهایی حاوی تگهای HTML مناسب است و اگر قرار است صرفا متن ارسال شود، میتوان TextFormat.Plain را انتخاب کرد.
در آخر، این پیام به SmtpClient جهت ارسال نهایی، فرستاده میشود. این SmtpClient هرچند هم نام مشابه آن در System.Net.Mail است اما با آن یکی نیست و متعلق است به MailKit. در اینجا ابتدا LocalDomain تنظیم شدهاست. تنظیم این مورد اختیاری بوده و صرفا به SMTP سرور دریافت کنندهی ایمیلها، مرتبط است که آیا قید آنرا اجباری کردهاست یا خیر. تنظیمات اصلی SMTP Server در متد ConnectAsync ذکر میشوند که شامل مقادیر host ،port و پروتکل ارسالی هستند.
ارسال ایمیل به SMTP pickup folder
روشی که تا به اینجا بررسی شد، جهت ارسال ایمیلها به یک SMTP Server واقعی کاربرد دارد. اما در حین توسعهی محلی برنامه میتوان ایمیلها را در داخل یک پوشهی موقتی ذخیره و آنها را توسط برنامهی Outlook (و یا حتی مرورگر Firefox) بررسی و بازبینی کامل کرد.
در این حالت تنها کاری را که باید انجام داد، جایگزین کردن قسمت ارسال ایمیل واقعی توسط SmtpClient در کدهای فوق، با قطعه کد ذیل است:
تولید فایلهای eml جهت «شبیه سازی ارسال ایمیل در ASP.Net» بسیار مفید هستند.
FAQ و منبع تکمیلی
افزودن وابستگیهای MailKit به برنامه
برای شروع به استفادهی از MailKit، میتوان بستهی نیوگت آنرا به فایل project.json برنامه معرفی کرد:
{ "dependencies": { "MailKit": "1.10.0" } }
استفاده از MailKit جهت تکمیل وابستگیهای ASP.NET Core Identity
قسمتی از ASP.NET Core Identity، شامل ارسال ایمیلهای «ایمیل خود را تائید کنید» است که آنرا میتوان توسط MailKit به نحو ذیل تکمیل کرد:
using System.Threading.Tasks; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using MailKit.Net.Smtp; using MailKit.Security; using MimeKit; namespace ASPNETCoreIdentitySample.Services.Identity { public class AuthMessageSender : IEmailSender, ISmsSender { public async Task SendEmailAsync(string email, string subject, string message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress("DNT", "do-not-reply@dotnettips.info")); emailMessage.To.Add(new MailboxAddress("", email)); emailMessage.Subject = subject; emailMessage.Body = new TextPart(TextFormat.Html) { Text = message }; using (var client = new SmtpClient()) { client.LocalDomain = "dotnettips.info"; await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false); await client.SendAsync(emailMessage).ConfigureAwait(false); await client.DisconnectAsync(true).ConfigureAwait(false); } } public Task SendSmsAsync(string number, string message) { // Plug in your SMS service here to send a text message. return Task.FromResult(0); } } }
در آخر، این پیام به SmtpClient جهت ارسال نهایی، فرستاده میشود. این SmtpClient هرچند هم نام مشابه آن در System.Net.Mail است اما با آن یکی نیست و متعلق است به MailKit. در اینجا ابتدا LocalDomain تنظیم شدهاست. تنظیم این مورد اختیاری بوده و صرفا به SMTP سرور دریافت کنندهی ایمیلها، مرتبط است که آیا قید آنرا اجباری کردهاست یا خیر. تنظیمات اصلی SMTP Server در متد ConnectAsync ذکر میشوند که شامل مقادیر host ،port و پروتکل ارسالی هستند.
ارسال ایمیل به SMTP pickup folder
روشی که تا به اینجا بررسی شد، جهت ارسال ایمیلها به یک SMTP Server واقعی کاربرد دارد. اما در حین توسعهی محلی برنامه میتوان ایمیلها را در داخل یک پوشهی موقتی ذخیره و آنها را توسط برنامهی Outlook (و یا حتی مرورگر Firefox) بررسی و بازبینی کامل کرد.
در این حالت تنها کاری را که باید انجام داد، جایگزین کردن قسمت ارسال ایمیل واقعی توسط SmtpClient در کدهای فوق، با قطعه کد ذیل است:
using (var stream = new FileStream($@"c:\smtppickup\email-{Guid.NewGuid().ToString("N")}.eml", FileMode.CreateNew)) { emailMessage.WriteTo(stream); }
FAQ و منبع تکمیلی
سالهای مدیدی است که به طراحی پایگاههای sql پرداخته و تجاربی آموختهایم. کتابها و مقالات زیادی در اینباره منتشر شدهاند. از اینرو در نحوه طراحی دیتابیسهای رابطهای اطلاعات زیادی کسب و مسائل زیادی را از این راه حل نمودهایم؛ ولی با ورود دیتابیسهای NoSql و تنوع زیاد آنها و روشهای متنوعی که هر کدام از آنها به طور جداگانه دارند باعث شد تجربه سالها فعالیت و مدل ذهنی که داشتیم به یکباره تغییر کند و گاها بیشتر باعث گیج شدن میگردد. از اینرو در این مقاله سعی داریم تکنیکها مدل سازی اسناد را در دیتابیس مونگو، بررسی کنیم و مزایا و معایب هر یک را برشماریم.
در دیتابیسهای قدیم، تمرکز بر روی نوشتن بود تا با کمترین افزونگی و تکرار و رعایت اصول ACID، اطلاعات را ذخیره نماییم. ولی در حال حاضر به دلیل دسترسی به فضاهای ذخیره سازی بزرگتر و همچنین افزایش ترافیک شبکه در واکشی دیتاها، قضیه عکس شده و تمرکز دیتابیسهای NoSql بر روی خواندن میباشد. پس باید فاکتورهای مدل سازی طوری باشد تا خواندن در سریعترین حد امکان قرار بگیرد. البته مواردی چون حذف و به روزرسانی هم باید در این مورد بررسی شوند.
ارتباط اسناد با یکدیگر:
ارتباط اسناد از دو طریق امکان پذیر است:
- حالت ارجاع : شماره سند یا Object Id را شامل شده و در صورتیکه به اطلاعاتی نیاز داشتید، باید اطلاعات آن را در یک درخواست جداگانه واکشی نمایید. چون مونگو شامل جوین نبوده و جوینها باید در سطح اپلیکیشن مدیریت شوند.
{ fname:'ali', lname:'yeganeh', accounts:[454354353,3455435] }
- حالت جاسازی سند (یا اسناد تو در تو) Embed : در این حالت سند مورد نظر اطلاعات سند دیگری را در درون خود نگه میدارد. در این حالت به هیچ جوینی نیازی نیست و اطلاعات وابسته، به همراه خود سند اصلی واکشی میشوند. این نکته باید مورد توجه قرار بگیرد که مونگو یک دیتابیس غیر اتمیک هست و در صورتیکه اصل دیتا تغییر کند، تغییر یا به روزرسانی در سندهای Embed انجام نخواهد شد و در صورت نیاز باید خودتان به طور دستی آن را کنترل نمایید.
{ fname:'ali', lname:'yeganeh', accounts:[ { username:"ali", password:"123" }, { username:"reza", password:"456" } ] }
مدل هایی با ارتباط یک به یک :
در این نوع مدل سازی، دو سند داریم که یکی از آنها Principle و دیگری Dependent محسوب میشود. برای ذخیره سازی آنها عموما از حالت Embed استفاده میشود. در این حالت چون ارتباط بین دو سند به صورت یک به یک میباشد، در واقع این امکان وجود دارد تا سند مادری به طور جداگانه وجود نداشته باشد و همان سند به صورت Embed ذخیره میشود. در این حالت مشکلی از لحاظ اتمیک نبودن مونگو پیش نمیاید و ویرایش راحتتری خواهد داشت.
مدلهایی با ارتباط یک به چند:
این اسناد را میتوان به دو حالت بالا بر حسب نیازمندی سیستم ذخیره کرد. فرض کنید مثال زیر را که در سایت مونگو هم عنوان شدهاست، داریم:
book { name:'Scarlet Letter", Language:"English", Pages:124, ... } publisher { name : "Orielly", ... }
book { name:'Scarlet Letter", Language:"English", Pages:124, ..., publisher: { name : "Orielly", ... } }
نکات مثبت:
- در این حالت در صورتیکه واکشی هر کتاب به همراه اطلاعات ناشر را نیاز داشته باشیم و یا پرس وجوهای ترکیبی نیاز باشد، در سریعترین زمان ممکن واکشی انجام خواهد شد.
- درج و مدیریت آن راحتتر خواهد بود.
نکات منفی:
- در صورتیکه اطلاعات ناشر نیاز به تغییرات اساسی داشته باشد و باید در تمامی سندها اصلاح گردد، باید تمامی اسناد مربوط به اطلاعات کتاب به روزرسانی شوند که هزینه سنگینتری را خواهد داشت.
- دیتای تکراری زیادی ذخیره خواهد شد و در نتیجه حافظه بیشتری را میطلبد.
- در صورتیکه تنها به اطلاعات ناشر نیاز باشد و اطلاعات ناشر در سند دیگری وجود نداشته باشد و فقط در سند کتاب وجود داشته باشد، واکشی آن هزینه سنگینتری را خواهد طلبید. به همین جهت توصیه میشود در صورتیکه دیتای شما میتواند به صورت یک موجودیت مستقل هم عمل کند، اطلاعات آن در سند دیگری که من به آن سند اصلی میگویم ذخیره شوند تا نمونهها از روی آخرین ویرایش آن ساخته شوند و موقعیکه تنها به واکشی آن اطلاعات نیاز است، همانها بیرون کشیده شوند.
در روشی دیگری میتوان ارجاعی از ناشر را به شکل زیر در کتاب نگهداری کرد:
book { name:'Scarlet Letter", Language:"English", Pages:124, ..., publisher:1212121 }
- عدم وجود تکرار اطلاعات
- چون تنها یک سند برای ویرایش وجود دارد، نیازی به اصلاح اسناد توکار نیست و ویرایش، هزینه کمتری خواهد داشت.
نکات منفی:
- عدم وجود جوین: در صورتیکه نیاز به جوین بزرگی باشد، این نوع جوین باید در سطح برنامه شما انجام شود و هزینه بر خواهد بود.
نگهداری نام کتابها در ناشر
انعطاف مونگو برای ایجاد مدل، گزینههای زیادی را پیش رو میگذارد و واقعا مدلسازی را بیشتر از قبل، چالش برانگیز میکند. در حالت دیگر میتوان اطلاعات کتاب را به صورت ارجاع، در سند ناشر نگهداری کرد. به عنوان مثال زمانیکه نیاز داریم کتب منتشرشده یک ناشر را ببینیم، شاید این گزینه بهتر باشد. البته در این حالت باید بتوان ارجاعات به کتاب را در تعداد محدودی نگهداری کرد؛ در غیر این صورت با تعداد زیادی ارجاع که شاید هیچگاه نیازی هم به آنها نیست، خواهیم رسید و در این حالت شاید ارجاع به ناشر در سند کتاب بسیار بهتر به نظر برسد. البته میتوان در این حالت ناشر تنها به تعداد معدودی از آخرین کتابهایش دسترسی داشته باشد تا کاربر بتواند آخرین کتابهای منتشر شدهی ناشر را ببیند.
حال با اطلاعات بالا چگونه مدلسازی کنیم؟
همانطور که گفتیم ابتدا تمرکز شما باید برای خواندن اطلاعات باشد و سپس معیارهایی چون به روزرسانی نیز بررسی گردند. به عنوان نمونه اطلاعات یک پست در وبلاگ را در نظر بگیرید. این سند شامل سندهای توکاری چون دسته بندی، اطلاعات نویسنده، معیارهایی چون امتیازدهی و بخش نظرات میباشد. در این حالت چون همه عناصر قرار است با یکدیگر بیرون کشیده شوند و در واقع تنها با یک سند سروکار داریم، کار بسیار سریعتر و راحتتر است. پس این ساختار گزینه مناسبی برای نمایش است:
Post { title:"C#", body:"About C#", tags:['C#','.Net','microsoft'], Categories:[{name:'Programming'}], votes:[{rate:3,user:42342},{rate:5,user:423445},...], comments:[ { text:"my comment1", time:"10/2/1396",...}, ... ] }
حال این تصور را داشته باشید که ما تنها یک پست را نشان نمیدهیم و بلکه پستها به صورت یک لیست قرار است نمایش داده شوند و با گزینهی مشاهدهی مطلب میتوانیم یک پست را به صورت کامل ببینیم. در این صورت همه اطلاعات همانند قبل هستند، بجز بخش نظرات که دیگر در این حالت کاربردی ندارد و دیتای اضافی است که به ناچار باید خوانده شود. پس در این حالت میگوییم این مدل برای خواندن مناسب نیست، چون باید تمام نظرات اسنادی که در لیست قرار دارند هم خوانده شوند. پس باید بخش نظرات را از سند پست وبلاگ جدا کنیم.
{ POST:45453, count:35, comments:[...] }
سپس میگوییم هر سند نهایتا 16 مگابایت اطلاعات را نگهداری میکند و هم اینکه تعداد نظرات ممکن است بسیار زیاد باشند. پس هر سند را به تعدادی نظر محدود میکنیم به این حالت میگویند داریم یک Bucket میسازیم و مثلا هر باکت را به 100 کامنت محدود میکنیم. تا به الان وضعیت طراحی بهتری نسبت به قبل پیدا کردیم:
{ post:345345, capacity:100, count:35, bucket:2, comments:[...] }
نکاتی که باید در حین طراحی در نظر بگیرید:
- همیشه به این نکته توجه داشته باشید که نباید بگذارید تعداد آرایههای یک سند خیلی بزرگ شوند. در غیر اینصورت کارآیی مونگو به خصوص در حین ویرایش سند پایین خواهد آمد. در حین ویرایش، اگر سندی از اندازهی خود بزرگتر نشود، مشکلی پیش نمیاید ولی اگر فضایی بیش از آنچه که قبلا داشته به آن اضافه شود، سند نیاز به جابجایی و گسترش فضا خواهد داشت. در این حالت باید مونگو سند را به جای دیگری که فضای کافی برای آن وجود دارد، انتقال بدهد و میزان Disk Fragment به طبع بالا خواهد رفت. همچنین اندیسهای آرایهای هم با جابجا شدن دیتا نیاز به، به روزرسانی خواهند داشت و زمانی هم صرف به روزرسانی اندیسها خواهد شد.
- مدیر محصول مونگو اظهار نظر صریحی در این مورد نکردهاست، ولی به نظر میرسد نوع فرمت BSON از یک اسکن خطی در حافظه استفاده میکند و زمان بیشتری صرف پیدا کردن المانهای انتهایی در آرایه خواهد شد؛ پس بیشتر عملیات در این نوع سند، با کندی مواجه خواهند شد. با توجه به کامنتهایی که در سایتها و شبکههای اجتماعی یافت شدهاست، آرایه ای با بیش از صدهزار آیتم ساده میتواند آسیب زا باشد؛ به همین دلیل توصیه میشود که اگر بیش از صدهزار آیتم نیاز است، از همان حالت Bucket استفاده شود.
- استفاده از اندیسها هم سابقهی دیرینهای داشته و سعی کنید کوئری هایی بزنید که بر اساس اندیسهای تعریف شده باشند تا واکشی دیتا سریعتر شود. پس نحوه کوئری نویسی و انتخاب فیلدی که اندیس میشود بسیار مهم است.
- استفاده از Projection تاثیری بر خواندن اسناد ندارد و هر سند به طور کامل واکشی میشود. projection تنها در بارهی ترافیک یا انتقال حجم کمتری از اطلاعات به سمت کلاینت تاثیرگذار میباشد. پس استفاده از projection بجای جدا سازی اسناد را دنبال نکنید.
در ادامه، مراحل ارتقاء پروژههای قدیمی MVC3 را به ساختار جدید پروژههای MVC4 مرور خواهیم کرد.
1) نصب پیشنیاز
الف) نصب VS 2012
و یا
ب) نصب بسته MVC4 مخصوص VS 2010 (این مورد جهت سرورهای وب نیز توصیه میشود)
پس از نصب باید به این نکته دقت داشت که پوشههای زیر حاوی اسمبلیهای جدید MVC4 هستند و نیازی نیست الزاما این موارد را از NuGet دریافت و نصب کرد:
پس از نصب پیشنیازها
2) نیاز است نوع پروژه ارتقاء یابد
به پوشه پروژه MVC3 خود مراجعه کرده و تمام فایلهای csproj و web.config موجود را با یک ادیتور متنی باز کنید (از خود ویژوال استودیو استفاده نکنید، زیرا نیاز است محتوای فایلهای پروژه نیز دستی ویرایش شوند).
در فایلهای csproj (یا همان فایل پروژه؛ که vbproj هم میتواند باشد) عبارت
را جستجو کرده و با
جایگزین کنید. به این ترتیب نوع پروژه به MVC4 تبدیل میشود.
3) به روز رسانی شماره نگارشهای قدیمی
سپس تعاریف اسمبلیهای قدیمی نگارش سه MVC و نگارش یک Razor را یافته (در تمام فایلها، چه فایلهای پروژه و چه تنظیمات):
و اینها را با نگارش چهار MVC و نگارش دو Razor جایگزین کنید:
این کارها را با replace in all open documents توسط notepad plus-plus به سادگی میتوان انجام داد.
4) به روز رسانی مسیرهای قدیمی
به علاوه اگر در پروژههای خود از اسمبلیهای قدیمی به صورت مستقیم استفاده شده:
اینها را یافته و به نگارش MVC4 و Razor2 تغییر دهید:
5) به روز رسانی قسمت appSettings فایلهای کانفیگ
در کلیه فایلهای web.config برنامه، webpages:Version را یافته و شماره نگارش آنرا از یک به دو تغییر دهید:
همچنین یک سطر جدید PreserveLoginUrl را نیز مطابق تنظیم فوق اضافه نمائید.
6) رسیدگی به وضعیت اسمبلیهای شرکتهای ثالث
ممکن است در این زمان از تعدادی کامپوننت و اسمبلی MVC3 تهیه شده توسط شرکتهای ثالث نیز استفاده نمائید. برای اینکه این اسمبلیها را وادار نمائید تا از نگارشهای MVC4 و Razor2 استفاده کنند، نیاز است bindingRedirectهای زیر را به فایلهای web.config برنامه اضافه کنید (در فایل کانفیگ ریشه پروژه):
اکنون فایل solution را در VS.NET گشوده و یکبار گزینه rebuild را انتخاب کنید تا پروژه مجددا بر اساس اسمبلیهای جدید معرفی شده ساخته شود.
7) استفاده از NuGet برای به روز رسانی بستههای نصب شده
یک سری از بستههای تشکیل دهنده MVC3 مانند موارد ذیل نیز به روز شدهاند که لازم است از طریق NuGet دریافت و جایگزین شوند:
Unobtrusive.Ajax.2
Unobtrusive.Validation.2
Web.Optimization.1.0.0
و ....
برای اینکار در solution explorer روی references کلیک راست کرده و گزینه Manage NuGet Packages را انتخاب کنید. در صفحه باز شده گزینه updates/all را انتخاب کرده و مواردی را که لیست میکند به روز نمائید (شامل جی کوئری، EF، structureMap و غیره خواهد بود).
8) اضافه کردن یک فضای نام جدید
بسته Web Optimization را از طریق NuGet دریافت کنید (برای یافتن آن bundling را جستجو کنید؛ نام کامل آن Microsoft ASP.NET Web Optimization Framework 1.0.0 است). این مورد به همراه پوشه MVC4 نیست و باید از طریق NuGet دریافت و نصب شود. (البته پروژههای جدید MVC4 شامل این مورد هستند)
در فایل وب کانفیگ، فضای نام System.Web.Optimization را نیز اضافه نمائید:
پس از ارتقاء
اولین مشکلی که مشاهده شد:
بعد از rebuild به مقدار پارامتر salt که به نحو زیر در MVC3 تعریف شده بود، ایراد خواهد گرفت:
Salt را در MVC4 منسوخ شده معرفی کردهاند: (^)
علت هم این است که salt را اینبار به نحو صحیحی خودشان در پشت صحنه تولید و اعمال میکنند. بنابراین این یک مورد را کلا از کدهای خود حذف کنید که نیازی نیست.
مشکل بعدی:
در EF 5 جای یک سری از کلاسها تغییر کرده. مثلا ویژگیهای ForeignKey، ComplexType و ... به فضای نام System.ComponentModel.DataAnnotations.Schema منتقل شدهاند. در همین حد تغییر جهت کامپایل مجدد کدها کفایت میکند.
همچنین فایلهای پروژه موجود را باز کرده و EntityFramework, Version=4.1.0.0 را جستجو کنید. نگارش جدید 4.4.0.0 است که باید اصلاح شود (این موارد را بهتر است توسط یک ادیتور معمولی خارج از VS.NET ویرایش کنید).
در زمان نگارش این مطلب EF Mini Profiler با EF 5 سازگار نیست. بنابراین اگر از آن استفاده میکنید نیاز است غیرفعالش کنید.
اولین استفاده از امکانات جدید MVC4:
استفاده از امکانات System.Web.Optimization که ذکر گردید، میتواند اولین تغییر مفید محسوب شود.
برای اینکه با نحوه کار آن بهتر آشنا شوید، یک پروژه جدید MVC4 را در VS.NET (از نوع basic) آغاز کنید. به صورت خودکار یک پوشه جدید را به نام App_Start به ریشه پروژه اضافه میکند. داخل آن فایل مثال BundleConfig قرار دارد. این کلاس در فایل global.asax برنامه نیز ثبت شدهاست. باید دقت داشت در حالت دیباگ (compilation debug=true در وب کانفیگ) تغییر خاصی را ملاحظه نخواهید کرد.
تمام اینها خوب؛ اما من به نحو زیر از این امکان جدید استفاده میکنم:
کلاس BundleConfig فوق را به مجموعه کلاسهای کمکی خود اضافه کنید.
چند نکته مهم در این کلاس وجود دارد:
الف) توسط AsIsBundleOrderer فایلها به همان ترتیبی که به سیستم اضافه میشوند، در حاصل نهایی ظاهر خواهند شد. حالت پیش فرض مرتب سازی، بر اساس حروف الفباء است و ... خصوصا برای اسکریپتهایی که ترتیب معرفی آنها مهم است، مساله ساز خواهد بود.
ب)BundleTable.EnableOptimizations سبب میشود تا حتی در حالت debug نیز فشرده سازی را مشاهده کنید.
ج) متدهای کمکی تعریف شده این امکان را میدهند تا بدون نیاز به کامپایل مجدد پروژه، به سادگی در کدهای Razor بتوانید اسکریپتها را اضافه کنید.
سپس نحوه جایگزینی تعاریف قبلی موجود در فایلهای Razor با سیستم جدید، به نحو زیر است:
پارامتر اول این متدها، سبب تعریف خودکار routing میشود. برای مثال اولین تعریف، آدرس خودکار زیر را تولید میکند:
بنابراین تعریف دقیق آن مهم است. خصوصا اگر فایلهای شما در پوشهها و زیرپوشههای متعددی قرار گرفته نمیتوان تمام آنها را در طی یک مرحله معرفی نمود. هر سطح را باید از طریق یک بار معرفی به سیستم اضافه کرد. مثلا اگر یک زیر پوشه به نام noty دارید (Content/noty)، چون در یک سطح و زیرپوشه مجزا قرار دارد، باید نحوه تعریف آن به صورت زیر باشد:
این مورد خصوصا در مسیریابی تصاویر مرتبط با اسکریپتها و شیوه نامهها مؤثر است؛ وگرنه این تصاویر تعریف شده در فایلهای CSS یافت نخواهند شد (تمام مثالهای موجود در وب با این مساله مشکل دارند و فرض آنها بر این است که کلیه فایلهای خود را در یک پوشه، بدون هیچگونه زیرپوشهای تعریف کردهاید).
پارامترهای بعدی، محل قرارگیری اسکریپتها و CSSهای برنامه هستند و همانطور که عنوان شد اینبار با خیال راحت میتوانید ترتیب معرفی خاصی را مدنظر داشته باشید؛ زیرا توسط AsIsBundleOrderer به صورت پیش فرض لحاظ خواهد شد.
1) نصب پیشنیاز
الف) نصب VS 2012
و یا
ب) نصب بسته MVC4 مخصوص VS 2010 (این مورد جهت سرورهای وب نیز توصیه میشود)
پس از نصب باید به این نکته دقت داشت که پوشههای زیر حاوی اسمبلیهای جدید MVC4 هستند و نیازی نیست الزاما این موارد را از NuGet دریافت و نصب کرد:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies
2) نیاز است نوع پروژه ارتقاء یابد
به پوشه پروژه MVC3 خود مراجعه کرده و تمام فایلهای csproj و web.config موجود را با یک ادیتور متنی باز کنید (از خود ویژوال استودیو استفاده نکنید، زیرا نیاز است محتوای فایلهای پروژه نیز دستی ویرایش شوند).
در فایلهای csproj (یا همان فایل پروژه؛ که vbproj هم میتواند باشد) عبارت
{E53F8FEA-EAE0-44A6-8774-FFD645390401}
{E3E379DF-F4C6-4180-9B81-6769533ABE47}
3) به روز رسانی شماره نگارشهای قدیمی
سپس تعاریف اسمبلیهای قدیمی نگارش سه MVC و نگارش یک Razor را یافته (در تمام فایلها، چه فایلهای پروژه و چه تنظیمات):
System.Web.Mvc, Version=3.0.0.0 System.Web.WebPages, Version=1.0.0.0 System.Web.Helpers, Version=1.0.0.0 System.Web.WebPages.Razor, Version=1.0.0.0
System.Web.Mvc, Version=4.0.0.0 System.Web.WebPages, Version=2.0.0.0 System.Web.Helpers, Version=2.0.0.0 System.Web.WebPages.Razor, Version=2.0.0.0
4) به روز رسانی مسیرهای قدیمی
به علاوه اگر در پروژههای خود از اسمبلیهای قدیمی به صورت مستقیم استفاده شده:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 3\Assemblies
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies
5) به روز رسانی قسمت appSettings فایلهای کانفیگ
در کلیه فایلهای web.config برنامه، webpages:Version را یافته و شماره نگارش آنرا از یک به دو تغییر دهید:
<appSettings> <add key="webpages:Version" value="2.0.0.0" /> <add key="PreserveLoginUrl" value="true" /> </appSettings>
6) رسیدگی به وضعیت اسمبلیهای شرکتهای ثالث
ممکن است در این زمان از تعدادی کامپوننت و اسمبلی MVC3 تهیه شده توسط شرکتهای ثالث نیز استفاده نمائید. برای اینکه این اسمبلیها را وادار نمائید تا از نگارشهای MVC4 و Razor2 استفاده کنند، نیاز است bindingRedirectهای زیر را به فایلهای web.config برنامه اضافه کنید (در فایل کانفیگ ریشه پروژه):
<configuration> <!--... elements deleted for clarity ...--> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="4.0.0.0"/> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
7) استفاده از NuGet برای به روز رسانی بستههای نصب شده
یک سری از بستههای تشکیل دهنده MVC3 مانند موارد ذیل نیز به روز شدهاند که لازم است از طریق NuGet دریافت و جایگزین شوند:
Unobtrusive.Ajax.2
Unobtrusive.Validation.2
Web.Optimization.1.0.0
و ....
برای اینکار در solution explorer روی references کلیک راست کرده و گزینه Manage NuGet Packages را انتخاب کنید. در صفحه باز شده گزینه updates/all را انتخاب کرده و مواردی را که لیست میکند به روز نمائید (شامل جی کوئری، EF، structureMap و غیره خواهد بود).
8) اضافه کردن یک فضای نام جدید
بسته Web Optimization را از طریق NuGet دریافت کنید (برای یافتن آن bundling را جستجو کنید؛ نام کامل آن Microsoft ASP.NET Web Optimization Framework 1.0.0 است). این مورد به همراه پوشه MVC4 نیست و باید از طریق NuGet دریافت و نصب شود. (البته پروژههای جدید MVC4 شامل این مورد هستند)
در فایل وب کانفیگ، فضای نام System.Web.Optimization را نیز اضافه نمائید:
<pages> <namespaces> <add namespace="System.Web.Optimization" /> </namespaces> </pages>
پس از ارتقاء
اولین مشکلی که مشاهده شد:
بعد از rebuild به مقدار پارامتر salt که به نحو زیر در MVC3 تعریف شده بود، ایراد خواهد گرفت:
[ValidateAntiForgeryToken(Salt = "data123")]
علت هم این است که salt را اینبار به نحو صحیحی خودشان در پشت صحنه تولید و اعمال میکنند. بنابراین این یک مورد را کلا از کدهای خود حذف کنید که نیازی نیست.
مشکل بعدی:
در EF 5 جای یک سری از کلاسها تغییر کرده. مثلا ویژگیهای ForeignKey، ComplexType و ... به فضای نام System.ComponentModel.DataAnnotations.Schema منتقل شدهاند. در همین حد تغییر جهت کامپایل مجدد کدها کفایت میکند.
همچنین فایلهای پروژه موجود را باز کرده و EntityFramework, Version=4.1.0.0 را جستجو کنید. نگارش جدید 4.4.0.0 است که باید اصلاح شود (این موارد را بهتر است توسط یک ادیتور معمولی خارج از VS.NET ویرایش کنید).
در زمان نگارش این مطلب EF Mini Profiler با EF 5 سازگار نیست. بنابراین اگر از آن استفاده میکنید نیاز است غیرفعالش کنید.
اولین استفاده از امکانات جدید MVC4:
استفاده از امکانات System.Web.Optimization که ذکر گردید، میتواند اولین تغییر مفید محسوب شود.
برای اینکه با نحوه کار آن بهتر آشنا شوید، یک پروژه جدید MVC4 را در VS.NET (از نوع basic) آغاز کنید. به صورت خودکار یک پوشه جدید را به نام App_Start به ریشه پروژه اضافه میکند. داخل آن فایل مثال BundleConfig قرار دارد. این کلاس در فایل global.asax برنامه نیز ثبت شدهاست. باید دقت داشت در حالت دیباگ (compilation debug=true در وب کانفیگ) تغییر خاصی را ملاحظه نخواهید کرد.
تمام اینها خوب؛ اما من به نحو زیر از این امکان جدید استفاده میکنم:
using System.Collections.Generic; using System.IO; using System.Web; using System.Web.Optimization; namespace Common.WebToolkit { /// <summary> /// A custom bundle orderer (IBundleOrderer) that will ensure bundles are /// included in the order you register them. /// </summary> public class AsIsBundleOrderer : IBundleOrderer { public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files) { return files; } } public static class BundleConfig { private static void addBundle(string virtualPath, bool isCss, params string[] files) { BundleTable.EnableOptimizations = true; var existing = BundleTable.Bundles.GetBundleFor(virtualPath); if (existing != null) return; var newBundle = isCss ? new Bundle(virtualPath, new CssMinify()) : new Bundle(virtualPath, new JsMinify()); newBundle.Orderer = new AsIsBundleOrderer(); foreach (var file in files) newBundle.Include(file); BundleTable.Bundles.Add(newBundle); } public static IHtmlString AddScripts(string virtualPath, params string[] files) { addBundle(virtualPath, false, files); return Scripts.Render(virtualPath); } public static IHtmlString AddStyles(string virtualPath, params string[] files) { addBundle(virtualPath, true, files); return Styles.Render(virtualPath); } public static IHtmlString AddScriptUrl(string virtualPath, params string[] files) { addBundle(virtualPath, false, files); return Scripts.Url(virtualPath); } public static IHtmlString AddStyleUrl(string virtualPath, params string[] files) { addBundle(virtualPath, true, files); return Styles.Url(virtualPath); } } }
چند نکته مهم در این کلاس وجود دارد:
الف) توسط AsIsBundleOrderer فایلها به همان ترتیبی که به سیستم اضافه میشوند، در حاصل نهایی ظاهر خواهند شد. حالت پیش فرض مرتب سازی، بر اساس حروف الفباء است و ... خصوصا برای اسکریپتهایی که ترتیب معرفی آنها مهم است، مساله ساز خواهد بود.
ب)BundleTable.EnableOptimizations سبب میشود تا حتی در حالت debug نیز فشرده سازی را مشاهده کنید.
ج) متدهای کمکی تعریف شده این امکان را میدهند تا بدون نیاز به کامپایل مجدد پروژه، به سادگی در کدهای Razor بتوانید اسکریپتها را اضافه کنید.
سپس نحوه جایگزینی تعاریف قبلی موجود در فایلهای Razor با سیستم جدید، به نحو زیر است:
@using Common.WebToolkit <link href="@BundleConfig.AddStyleUrl("~/Content/blueprint/print", "~/Content/blueprint/print.css")" rel="stylesheet" type="text/css" media="print"/> @BundleConfig.AddScripts("~/Scripts/js", "~/Scripts/jquery-1.8.0.min.js", "~/Scripts/jquery.unobtrusive-ajax.min.js", "~/Scripts/jquery.validate.min.js") @BundleConfig.AddStyles("~/Content/css", "~/Content/Site.css", "~/Content/buttons.css")
http://site/Content/blueprint/print?v=hash
@BundleConfig.AddStyles("~/Content/noty/css", "~/Content/noty/jquery.noty.css", "~/Content/noty/noty_theme_default.css")
پارامترهای بعدی، محل قرارگیری اسکریپتها و CSSهای برنامه هستند و همانطور که عنوان شد اینبار با خیال راحت میتوانید ترتیب معرفی خاصی را مدنظر داشته باشید؛ زیرا توسط AsIsBundleOrderer به صورت پیش فرض لحاظ خواهد شد.