اشتراکها
10 شغل برتر IT در کشور آمریکا
اشتراکها
خود آزمایی ES6
اشتراکها
کار با Enum در MVC 5.2
اشتراکها
روز تولد CSS
پیاده سازی جستجوی بر روی این گرید، شامل موارد زیر است:
اضافه کردن دو خاصیت جدید به کلاس PagedQueryModel سمت کلاینت جهت مشخص سازی ستونی که قرار است بر روی آن جستجو انجام شود و همچنین مقدار آن:
سپس به ProductsListComponent دو متد زیر را اضافه میکنیم:
اولی کار جستجو را انجام میدهد و دومی بازگشت حالت گرید به وضعیت اول آن است. متد getPagedProductsList قابلیت واکشی خودکار اطلاعات دو خاصیت جدیدی را که اضافه کردیم دارد و نیازی به تنظیمات اضافهتری ندارد. یعنی filterByColumn و filterByValue را به صورت خودکار به سمت سرور ارسال میکند.
پس از آن، قالب این گرید (products-list.component.html) جهت افزودن جستجو، به صورت زیر تغییر میکند:
که در آن queryModel.filterByColumn و queryModel.filterByValue از کاربر دریافت میشوند. همچنین دو متد doFilter و resetFilter را نیز فراخوانی میکند.
با این شکل:
تغییرات سمت سرور آن نیز به صورت ذیل است:
ابتدا IPagedQueryModel را با همان دو خاصیت جدید ستون فیلتر شونده و مقدار آن، تکمیل میکنیم:
از این دو خاصیت جدید، جهت افزودن متد اعمال جستجو، همانند متد ApplyOrdering که پیشتر تعریف شد، استفاده میکنیم:
در اینجا همان columnsMap مورد استفاده در متد ApplyOrdering جهت نگاشت نامهای رشتهای ستونها به معادل Expression آنها استفاده شدهاست.
در آخر، به کنترلر ProductController و اکشن متد GetPagedProducts آن مراجعه کرده و پیش از ApplyOrdering، متد جدید ApplyFiltering فوق را اضافه میکنیم:
کدهای کامل این تغییرات را از اینجا میتوانید دریافت کنید.
اضافه کردن دو خاصیت جدید به کلاس PagedQueryModel سمت کلاینت جهت مشخص سازی ستونی که قرار است بر روی آن جستجو انجام شود و همچنین مقدار آن:
export class PagedQueryModel { constructor( // ... public filterByColumn: string, public filterByValue: string, ) { } }
doFilter() { this.queryModel.page = 1; this.getPagedProductsList(); } resetFilter() { this.queryModel.page = 1; this.queryModel.filterByColumn = ""; this.queryModel.filterByValue = ""; this.getPagedProductsList(); }
پس از آن، قالب این گرید (products-list.component.html) جهت افزودن جستجو، به صورت زیر تغییر میکند:
<div class="panel panel-default"> <div class="panel-body"> <div class="form-group"> <input type="text" [(ngModel)]="queryModel.filterByValue" placeholder="Search For ..." class="form-control" /> </div> <div class="form-group"> <select class="form-control" name="filterColumn" [(ngModel)]="queryModel.filterByColumn"> <option value="">Filter by ...</option> <option *ngFor="let column of columns" [value]="column.propertyName"> {{ column.title }} </option> </select> </div> <button class="btn btn-primary" type="button" (click)="doFilter()">Search</button> <button class="btn btn-default" type="button" (click)="resetFilter()">Reset</button> </div> </div>
با این شکل:
تغییرات سمت سرور آن نیز به صورت ذیل است:
ابتدا IPagedQueryModel را با همان دو خاصیت جدید ستون فیلتر شونده و مقدار آن، تکمیل میکنیم:
public interface IPagedQueryModel { // .... string FilterByColumn { get; set; } string FilterByValue { get; set; } } public class ProductQueryViewModel : IPagedQueryModel { // ... other properties ... // ... public string FilterByColumn { get; set; } public string FilterByValue { get; set; } }
public static class IQueryableExtensions { public static IQueryable<T> ApplyFiltering<T>( this IQueryable<T> query, IPagedQueryModel model, IDictionary<string, Expression<Func<T, object>>> columnsMap) { if (string.IsNullOrWhiteSpace(model.FilterByValue) || !columnsMap.ContainsKey(model.FilterByColumn)) { return query; } var func = columnsMap[model.FilterByColumn].Compile(); return query.Where(x => func(x).ToString() == model.FilterByValue); }
در آخر، به کنترلر ProductController و اکشن متد GetPagedProducts آن مراجعه کرده و پیش از ApplyOrdering، متد جدید ApplyFiltering فوق را اضافه میکنیم:
var columnsMap = new Dictionary<string, Expression<Func<Product, object>>>() { ["productId"] = p => p.ProductId, ["productName"] = p => p.ProductName, ["isAvailable"] = p => p.IsAvailable, ["price"] = p => p.Price }; query = query.ApplyFiltering(queryModel, columnsMap); query = query.ApplyOrdering(queryModel, columnsMap);
کدهای کامل این تغییرات را از اینجا میتوانید دریافت کنید.
Micro Frontend چیست؟
micro frontend یک الگوی معماری (architecture pattern) میباشد؛ جایی که یک front-end app، به چند app کوچکتر تقسیم میشود و هر کدام از آنها به صورت مستقل توسعه داده و تست میشوند. مفهومی شبیه به مایکروسرویسها است؛ اما برای سورس کدهای یکپارچهی سمت کلاینت.
چرا؟
خیلی سخت است که بخواهیم روی سورس کدهای یکپارچه سمت کلاینت تست نویسی، بهروز رسانی و هم چنین نگهداری کنیم. این در حالی است که توانایی تیم را به منظور مستقل کار کردن بر روی بخشهای مختلفی از app، محدود میکند. شکستن یک app یکپارچه به micro frontendهای کوچکتر و قابل مدیریت، این امکان را فراهم میسازد که چندین تیم، به صورت مستقل کار کنند و از فریم ورکهای ترجیحی خود استفاده کنند.
چگونه؟
اساسا 3 راه برای ادغام کردن ماژولهای micro frontend با container app وجود دارد.
1-Server integration
هر micro frontend در یک وب سرور احتمالا جداگانه هاست شدهاست که مسئول رندر کردن و خدمت دادن markupهای مربوطه میباشد. به محض دریافت درخواستی از سمت مرورگر، container app در خواست را برای markup، با برقراری تماس به سرور، برای micro frontend مربوطه انجام میدهد.
این یک روش ایده آل نمیباشد؛ بهطوریکه چندین تماس به سرور برای رندر کردن محتوا در صفحه برقرار میشود و همچنین مستلزم پیاده سازی استراتژی cache، به منظور کاهش تاخیر است.
2-Compile time integration
container app دسترسی به کدهای micro frontendها را در طول توسعه و زمان کامپایل دارد. یکی از راههای انجام این روش این است که micro frontendها را به عنوان بستههای npm منتشر کنیم و سپس از آنها به عنوان یک وابستگی در container app استفاده کنیم.
در حالیکه راه اندازی و پیاده سازی، در این حالت ساده است، اما یک وابستگی محکم بین container app و micro frontend وجود دارد. هر زمانکه یک micro frontend بروزرسانی میشود، نیاز است که container app ، به منظور یکپارچه کردن بروز رسانی، دوباره استقرار یابد.
3-Run time integration
container app دسترسی به کدهای micro frontendها را زمانیکه در مرورگر اجرا میشوند، دارد. یکی از راههای انجام این روش، استفاده از پلاگین Module Federation مربوط به Webpack است که مراقب ساختن، در دسترس قرار دادن و استفاده از وابستگیها در زمان اجرا میباشد (در ادامه، این حالت را با جزئیات بیشتری مورد بررسی قرار خواهیم داد).
در این حالت وابستگی بین container app و micro frontendها وجود ندارد و یکپارچگی در زمان اجرا اتفاق میافتد. هر micro frontend میتواند به صورت مستقل توسعه داده شود و استقرار یابد، بدون اینکه container app را دوباره استقرار دهیم.
در این حالت راه اندازی در مقایسه با compile-time integration پیچیدهتر است.
Webpack Module Federation Plugin
پلاگین module federation در Webpack 5.0 معرفی شد که امکان توسعه appهای micro frontend را با بارگذاری پویای کدهای appهای micro frontend را در container app ، فراهم میسازد.
همچنین این امکان را فراهم میکند که dependency ها را بین remote appها و host app، به منظور جلوگیری از کدهای تکراری و کاهش دادن سایز build، به اشتراک بگذاریم.
در این مقاله ما یک مثال را بررسی خواهیم کرد که شامل دو micro frontend ساده میباشد و سپس آنها را در یک host app ادغام میکنیم.
ساختار نهایی بهصورت زیر خواهد بود:
ایجاد کردن اولین Remote app
یک پوشه را به نام remote1 ایجاد کنید و سپس یک فایل را به نام package.json، در پوشهی ایجاد شده، با دستور زیر ایجاد کنید.
npm init --yes
پوشهی ایجاد شده را در code editor خود باز کنید (من از vs code استفاده میکنم) و سپس وابستگیهای زیر را به فایل package.json اضافه کنید.
npm install html-webpack-plugin webpack webpack-cli webpack-dev-server --save
1) webpack/ webpack-CLI: برای استفاده از webpack و دستورات webpack CLI
2) html-webpack-plugin/webpack-devserver: سرور توسعه محلی با live reloading
و همچنین اسکریپت webpack serve را در بخش scripts، به منظور serve کردن application در مرورگر اضافه کنید.
اکنون فایل package.json شما همانند زیر میباشد:
{ "name": "remote1", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "webpack serve" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "html-webpack-plugin": "^5.3.2", "webpack": "^5.57.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.3.1" } }
<!DOCTYPE html> <html> <head> <title> Remote1(Localhost:7001) </title> </head> <body> <div id="dev-remote1"></div> </body> </html>
در ادامه یک پوشهی جدید را به نام src ایجاد کنید و دو فایل را با نامهای index.js و startup.js در آن قرار دهید. در ادامه به این دو فایل برمیگردیم و کدهای لازم را در آن قرار میدهیم.
یک فایل جدید را به نام webpack.config.js در ریشهی پروژه ایجاد میکنیم که در آن تنظیمات webpack را قرار میدهیم. در این فایل، دو پلاگین را به نامهای Webpack و Module Federation، اضافه میکنیم.
const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { mode: 'development', devServer: { port: 7001, }, plugins: [ new ModuleFederationPlugin({ name: 'remote1', filename: 'remoteEntry.js', exposes: { './RemoteApp1': './src/startup', }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], };
فایل remoteEntry.js شامل یک لیست از فایلهای در معرض قرار داده شدهی توسط remote app به همراه مسیرهای آنها میباشد. از این فایل در زمان ادغام کردن remote app در host app استفاده میشود.
index.js نقطهی ورودی remote app ما میباشد. به منظور جلوگیری از eager loading، کدهای startup را در یک js فایل جداگانه به نام startup.js قرار میدهیم. از این رو فایل index.js شامل فقط یک import میباشد.
import('./startup');
در درون فایل startup.js ، یک تابع به نام mount را export میکنیم. این تابع یک element را دریافت میکند؛ جائیکه قرار است خروجی app در آن قرار گیرد. در حالت development، خروجی در یک div نگهدارنده با شناسه dev-remote1 در فایل محلی index.html قرار میگیرد.
const mount = (el) => { el.innerHTML = '<div>Remote 1 Content</div>'; }; if (process.env.NODE_ENV === 'development') { const el = document.querySelector('#dev-remote1'); if (el) { mount(el); } } export { mount };
ایجاد کردن دومین Remote app
در اینجا نیز مراحل، دقیقا شبیه به ایجاد remote app قبلی میباشد. یک پوشه را به نام remote2 در کنار پوشهی remote1 ایجاد کنید و یک فایل را به نام package.json، با وابستگیهای معرفی شده ایجاد کنید و سپس دستور npm install را بزنید تا وابستگیها نصب شوند.
{ "name": "remote2", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "webpack serve" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "html-webpack-plugin": "^5.3.2", "webpack": "^5.57.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.3.1" } }
سپس یک فایل را با نام index.html و با محتوای زیر، در پوشهی public ایجاد کنید:
<!DOCTYPE html> <html> <head> <title> Remote2(Localhost:7002) </title> </head> <body> <div id="dev-remote2"></div> </body> </html>
در ادامه یک فایل را با نام webpack.config.js در ریشهی پروژهی remote2 ایجاد کنید و محتوای زیر را در آن قرار دهید. مطمئن باشید که پورت اجرایی برنامه 7002 میباشد.
const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { mode: 'development', devServer: { port: 7002, }, plugins: [ new ModuleFederationPlugin({ name: 'remote2', filename: 'remoteEntry.js', exposes: { './RemoteApp2': './src/startup', }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], };
فایل فایل index.js برای remote2
import('./startup');
فایل startup.js برای remote2
const mount = (el) => { el.innerHTML = '<div>Remote 2 Content</div>'; }; if (process.env.NODE_ENV === 'development') { const el = document.querySelector('#dev-remote2'); if (el) { mount(el); } } export { mount };
اکنون میتوانید با اجرای دستور npm start برنامه را بر روی پورت 7002 اجرا کنید . ( http://localhost:7002 )
ایجاد کردن Host app و یکپارچه کردن آن با Remote app ها
یک پوشهی جدید را به نام container در کنار دو پوشهی قبلی (remote1, remote2) ایجاد کنید. در پوشهی ایجاد شده، یک فایل را به نام package.json ایجاد کنید و محتوای زیر را در آن قرار دهید و سپس دستور npm install را بزنید تا لیست وابستگیها دریافت شود.
{ "name": "container", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "webpack serve" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "html-webpack-plugin": "^5.3.2", "webpack": "^5.57.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.3.1" } }
اکنون یک پوشه را به نام public، در ریشهی پروژه ایجاد کنید و یک فایل را به نام index.html، در آن قرار دهید. در این فایل دو div نگهدارنده به منظور هاست کردن محتوا، از هر remote app قرار دارد.
<!DOCTYPE html> <html> <head> <title>Host App (Localhost:7000)</title> </head> <body> <div id="remote1-app"></div> <div id="remote2-app"></div> </body> </html>
یک فایل را با نام webpack.config.js، با تنظیمات زیر در ریشهی پروژه قرار دهید. در اینجا پورت اجرایی برنامه، 7000 تعیین شدهاست:
const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { mode: 'development', devServer: { port: 7000, }, plugins: [ new ModuleFederationPlugin({ name: 'container', remotes: { remote1: 'remote1@http://localhost:7001/remoteEntry.js', remote2: 'remote2@http://localhost:7002/remoteEntry.js', }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], };
دقیقا مثل remote app ها، در فایل index.js مربوط به import ، host app زیر را انجام میدهیم:
import('./startup');
import { mount as remote1Mount } from 'remote1/RemoteApp1'; import { mount as remote2Mount } from 'remote2/RemoteApp2'; remote1Mount(document.querySelector('#remote1-app')); remote2Mount(document.querySelector('#remote2-app'));
در ادامه جهت مشاهده خروجی، app میزبان را با دستور npm start اجرا میکنیم. اکنون شما میتوانید خروجی remote app ها را در host app ببینید. لازم به ذکر است که در هنگام اجرای دستور npm start برای host app ، هر دو remote app ایجاد شده باید در حالت اجرا باشند.
ملاحظات
Module federation در Webpack نسخه 5 به بالا، در دسترس قرار دارد. شما میتوانید وابستگیهای بین remote app و host app را به اشتراک بگذارید. این کار را با اضافه کردن آنها به تنظیمات پلاگین module federation برای remote app و host app انجام دهید.
new ModuleFederationPlugin({ name: 'remote1', filename: 'remoteEntry.js', exposes: { './RemoteApp1': './src/startup', }, shared:['react', 'react-dom'] }),
ارتباط بین host و remote app میتواند از طریق callbackها انجام شود.
بازخوردهای دوره
انتقال خودکار Data Annotations از مدلها به ViewModelهای ASP.NET MVC به کمک AutoMapper
سلام
با توجه به بخش Other Notes در این مطلب استفاده همزمان از انتقال خودکار Data Annotations و تزریق وابستگیهای AutoMapper در لایه سرویس برنامه چگونه است؟
متشکرم
با توجه به بخش Other Notes در این مطلب استفاده همزمان از انتقال خودکار Data Annotations و تزریق وابستگیهای AutoMapper در لایه سرویس برنامه چگونه است؟
متشکرم