.NET 7 minimal API from scratch | FULL COURSE | clean architecture, repository pattern, CQRS MediatR
In this course I want to provide you a project structure and code organization to get you started with real .NET 7 minimal API projects. It's a full course on this topic where I start from creating and explaining the project structure, setting up different layers using EF Core, repository pattern, CQRS and MediatR. The biggest part of the video is however around the .NET 7 minimal API, taking you from the initial setup, explaining route handlers, implementing all CRUD operations and so on. Last but not least, this course walks you through the process of refactoring the .NET 7 minimal API so that it becomes readable, maintainable and scalable. At the end, you'll have a full project structure organized according to modern architectural patterns that you can take as a template for your own projects.
Contents
1. Intro: 00:00
2. Structuring the solution: 01:00
3. Coding the domain layer: 05:25
4. Coding the data access layer: 08:22
5. Creating repositories: 11:17
6. Adding migrations and database update: 22:30
7. CQRS with MediatR: 29:07
8. Route and rout handlers: 52:06
9. Dependency injection: 55:52
10. Implementing GET by ID : 57:40
11. Implementing POST route: 01:00:26
12. Implementing GET all route: 01:03:41
13. Implement PUT and DELETE: 01:04:57
14. Testing with Postman: 01:09:01
15. Is there a problem? 01:12:41
16. Refactoring service registrations: 01:15:49
17. Refactoring route registrations: 01:20:01
18. Automatic registration of endpoints: 01:26:28
19. Introducing route groups: 01:31:43
20. Extract lambdas into instance methods: 01:34:31
21: Model validation with endpoint filters: 01:45:58
22. Global exception handling: 01:55:10
23. Conclusions: 01:59:49
کتابخانه stimed
STIMED.js is a jQuery plugin for controlling CSS styles over time. There are almost endless possibilities for using timed CSS properties. You could change webpage colors to make them more calm at night and more contrast during daytime, or hide a specific element in a certain time, or just move an element in sync with time... Demo
مطالب پیشین مرتبط با لوسین را در اینجا میتوانید پیگیری کنید. آخرین نگارش آن که تا این تاریخ، 4.8 بتا است، با داتنت(Core) سازگار است و روش برپایی آغازین آن ... تغییرات قابل توجهی داشتهاست که خلاصهی آنها را در این مطلب بررسی خواهیم کرد.
1) بستههای جدید مورد نیاز
برای کار با لوسین جدید، نیاز است حداقل سهبستهی زیر را نصب کنیم تا به امکانات پایهای و کوئری گیریهای پیشرفتهی آن دسترسی داشته باشیم:
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016"/> <PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016"/> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016"/>
2) تهیه نگاشتهای لازم
فرض کنید شیء اصلی ما چنین ساختاری را دارد:
public class WhatsNewItemModel { public required int Id { set; get; } public required string OriginalTitle { set; get; } }
مرحلهی بعد کار با لوسین، تبدیل اشیاء سفارشی خود به شیء Document لوسین و برعکس است. به همین جهت به دو مپر برای این کارها نیاز است:
الف) نگاشتگر یک شیء سفارشی، به شیء Document
public static class LuceneDocumentMapper { public static Document MapToLuceneDocument(this WhatsNewItemModel post) { ArgumentNullException.ThrowIfNull(post); return [ new TextField(nameof(WhatsNewItemModel.OriginalTitle), post.OriginalTitle, Field.Store.YES), // Document StringField instances are sort of keywords, they are not analyzed, they indexed as is (in its original case). new StringField(nameof(WhatsNewItemModel.Id), post.Id.ToString(CultureInfo.InvariantCulture), Field.Store.YES), ]; } }
در اینجا یک متدالحاقی را تهیه کردهایم تا شیءای از نوع WhatsNewItemModel ما را به یک شیء Document لوسین، تبدیل کند.
چند نکته در اینجا حائز اهمیت هستند:
- در نگارش جدید لوسین، با اشیاء TextField و StringField جدید سروکار داریم و شیء قدیمی Field نگارشهای قبلی لوسین، منسوخ شده درنظر گرفته میشود.
- زمانی از شیء TextField استفاده میکنیم که قرار است توسط لوسین، تحلیل شده و در جستجوهای پیچیده استفاده شود.
- اگر فقط قرار است، مقداری را در این ایندکس ذخیره کنیم و قصد تحلیل آنها را نداریم و حداکثر یک کوئری سادهی یافتن اصل آنها، مدنظر ما است، باید از اشیاء StringField برای معرفی و نگاشت آنها استفاده کنیم (شبیه به کار با واژههای کلیدی).
- پرچم Field.Store.YES به این معنا است که اصل محتوای تحلیل شده نیز در ایندکس لوسین، درج شود. اگر این پرچم را به NO تنظیم کنیم، فقط تحلیل آن صورت گرفته و نتیجهی آن ذخیره میشود، که برای جستجوها مفید است؛ اما مقدار این فیلد دیگر قابل بازیابی نخواهد بود.
ب) نگاشتگر یک شیء Document لوسین، به یک شیء سفارشی
در زمان کوئری گرفتن از لوسین، خروجی نهایی یک شیء Document آن است که باید به شیء سفارشی مدنظر ما نگاشت شود:
public static class LuceneDocumentMapper { public static LuceneSearchResult MapToLuceneSearchResult(this Document document) { ArgumentNullException.ThrowIfNull(document); return new LuceneSearchResult { Id = document.Get(nameof(WhatsNewItemModel.Id), CultureInfo.InvariantCulture).ToInt(), OriginalTitle = document.Get(nameof(WhatsNewItemModel.OriginalTitle), CultureInfo.InvariantCulture) }; } }
نمونهای از این نگاشت را در متد الحاقی فوق مشاهده میکنید که توسط متد Get شیء Document قابل انجام است. بدیهی است خروجی این متد، یک رشتهاست و در صورت نیاز باید توسط ما کار تبدیلات ثانویه آنها انجام شود.
3) نیاز به یک تحلیلگر مناسب
لوسین برای تولید ایندکسهای جستجوی تمام متنی خود، از یک سری Analyzer استفاده میکنید که اگر سری پیشین مطالب مرتبط را مطالعه کنید، به نمونهی StandardAnalyzer آن خواهید رسید که هنوز هم معتبر و قابل استفادهاست و یا میتوان همانند سایت جاری، از یک LowerCaseHtmlStripAnalyzer استفاده کرد که این کارها را همزمان انجام میدهد:
الف) از یک لیست PersianStopwords.List برای حذف واژههای کم اهمیت زبان فارسی استفاده میکند. برای مثال ما نمیخواهیم که واژهی «ما» را با اهمیت شمرده و ایندکس کند و امثال آن.
ب) LowerCaseFilter را به متون دریافتی اعمال میکند. این کار در پشت صحنهی StandardAnalyzer توکار لوسین هم اعمال میشود. اگر با این موضوع آشنا نباشید، ممکن است در حین کوئری گرفتن، به نتیجهای نرسید! چون متن ارسالی به لوسین را ابتدا باید lower-case کنید و سپس آنرا کوئری بگیرید.
ج) HTMLStripCharFilter توکار لوسین هم به آن اعمال شدهاست. از این جهت که متن مقالات ما به همراه تگهای HTML ای هم هستند. این فیلتر کار حذف کردن آنها را در حین تحلیل، انجام میدهد و دیگر نیازی نیست تا ما خودمان متن ارسالی به لوسین را تمیز کنیم.
نکتهی مهم: این تحلیلگر ویژه، فقط باید به فیلدهایی از نوع TextField اعمال شود. اگر آنرا به StringField ها اعمال کنیم، دیگر قادر به کوئری گرفتن از آنها نخواهیم بود! چون تحلیلگر StringFieldها باید از نوع توکار KeywordAnalyzer ثبت و معرفی شود. این نوع فیلدها، حالت واژههای کلیدی را دارند (به همان صورتی که هست ثبت میشوند) و قرارنیست که توسط لوسین تحلیل ویژهای شوند. به همین جهت برای رسیدن به یک تحلیلگر ترکیبی که بتواند این دو نوع فیلد را با هم پوشش دهد و کار معرفی چندین نوع تحلیلگر را یکجا انجام دهد، نیاز به یک PerFieldAnalyzerWrapper جدید داریم:
_keywordAnalyzer = new KeywordAnalyzer(); _lowerCaseHtmlStripAnalyzer = new LowerCaseHtmlStripAnalyzer(LuceneVersion); _analyzer = new PerFieldAnalyzerWrapper(_lowerCaseHtmlStripAnalyzer, new Dictionary<string, Analyzer> { { nameof(WhatsNewItemModel.Id), _keywordAnalyzer } });
PerFieldAnalyzerWrapper در حقیقت برای تمام فیلدهایی که در قسمت دیکشنری فوق، ذکر نشدهاند، از LowerCaseHtmlStripAnalyzer استفاده میکند. برای مابقی موارد از KeywordAnalyzer کمک خواهد گرفت.
4) روش صحیح راه اندازی reader و writer های ایندکس لوسین جدید
کار با لوسین به حدی سریع است که از کیفیت آن شگفت زده خواهید شد! اما ... بهشرطی که بدانید دقیقا به چه صورتی باید نویسنده و خوانندهی ایندکسهای آنرا مدیریت کنید. اکثر مثالهایی را که بر روی اینترنت پیدا میکنید، به همراه متدهایی هستند که مدام در حال گشودن و dispose این نویسندهها و خوانندههای ایندکس هستند که ... این مثالها، روش کار صحیح با لوسین نیستند! و به شدت آنرا کند میکنند.
نکتهی مهمی که این مثالها به آن توجهی نکردهاند، «thread-safe» بودن نویسنده و خوانندهی ایندکس لوسین است. یعنی میتوان یک نمونه از اینها را در ابتدای کار برنامه ایجاد کرد و تا آخر کار برنامه، بدون نیاز به نمونه سازی مجدد و باز و بسته کردن آنها، بارها مورد استفادهی مجدد قرار داد و هیچ تداخلی هم ندارند و از قسمتهای مختلف برنامه هم قابل دسترسی هستند.
به همین جهت باید یک سرویس مرکزی را برای اینکار تدارک دید که طول عمر آن، حتما Singleton باشد تا بتواند نویسنده و خوانندهی ایندکس لوسین را فقط یکبار نمونه سازی و ایجاد کرده و تا پایان کار برنامه، زنده نگه دارد (کدهای کامل این کلاس را در اینجا میتوانید مطالعه کنید):
public class FullTextSearchService : IFullTextSearchService { private const LuceneVersion LuceneVersion = Lucene.Net.Util.LuceneVersion.LUCENE_48; private readonly Analyzer _analyzer; private readonly IAppFoldersService _appFoldersService; private readonly FSDirectory _fsDirectory; // IndexWriter instances are completely thread safe, meaning multiple threads can call any of its methods, concurrently. private readonly IndexWriter _indexWriter; private readonly KeywordAnalyzer _keywordAnalyzer; private readonly ILogger<FullTextSearchService> _logger; private readonly LowerCaseHtmlStripAnalyzer _lowerCaseHtmlStripAnalyzer; // Safely shares IndexSearcher instances across multiple threads, while periodically reopening. private readonly SearcherManager _searcherManager; private bool _isDisposed; public FullTextSearchService(IAppFoldersService appFoldersService, ILogger<FullTextSearchService> logger) { _appFoldersService = appFoldersService ?? throw new ArgumentNullException(nameof(appFoldersService)); _logger = logger; _keywordAnalyzer = new KeywordAnalyzer(); _lowerCaseHtmlStripAnalyzer = new LowerCaseHtmlStripAnalyzer(LuceneVersion); _analyzer = new PerFieldAnalyzerWrapper(_lowerCaseHtmlStripAnalyzer, new Dictionary<string, Analyzer> { // Document StringField instances are sort of keywords, they are not analyzed, they indexed as is (in its original case). // But StandardAnalyzer applies lower case filter to a query. // We can fix this by using KeywordAnalyzer with our query parser. { nameof(WhatsNewItemModel.Id), _keywordAnalyzer }, { nameof(WhatsNewItemModel.DocumentTypeIdHash), _keywordAnalyzer }, { nameof(WhatsNewItemModel.DocumentContentHash), _keywordAnalyzer } }); _fsDirectory = FSDirectory.Open(_appFoldersService.LuceneIndexFolderPath); _indexWriter = new IndexWriter(_fsDirectory, new IndexWriterConfig(LuceneVersion, _analyzer)); _searcherManager = new SearcherManager(_indexWriter, applyAllDeletes: true, searcherFactory: null); }
این سرویس، یک سرویس Singleton است که نحوهی آغاز و شروع به کار با اشیاء لوسین را در سازندهی آن مشاهده میکنید.
توضیحات:
الف) در اینجا، روش نمونه سازی PerFieldAnalyzerWrapper را که پیشتر در مورد آن بحث شد، مشاهده میکنید.
ب) سپس یک IndexWriter، نمونه سازی میشود که از تحلیلگر ترکیبی ما استفاده میکند.
ج) در ادامه یک SearcherManager جدید را مشاهده میکنید که با IndexWriter برنامه هماهنگ است و هر زمانیکه سندی به لوسین اضافه میشود، قادر به کوئری گرفتن از آن هم خواهیم بود.
نکتهی مهم: طول عمر تمام این موارد، با طول عمر کلاس سرویس جاری، یکی است. یعنی تنها یکبار در طول عمر برنامه نمونه سازی شده و تا پایان کار آن، زنده نگه داشته میشوند.
5) روش افزودن یک سند به ایندکس لوسین و سپس به روز رسانی آن
اکنون با استفاده از نگاشتگرهایی که در ابتدای بحث تهیه کردیم و همچنین شیء IndexWriter فوق، به صورت زیر میتوان یک شیء سفارشی خود را به ایندکس لوسین اضافه کنیم:
_indexWriter.AddDocument(post.MapToLuceneDocument()); _indexWriter.Flush(triggerMerge: true, applyAllDeletes: true); _indexWriter.Commit();
و یا اگر خواستیم سند موجودی را به روز کنیم، روش کار به شکل زیر است:
_indexWriter.UpdateDocument(new Term(nameof(WhatsNewItemModel.Id), post.Id.ToString()), post.MapToLuceneDocument());
new Term، در حقیقت یک کوئری جدید را سبب میشود که توسط آن سندی یافت شده، در پشت صحنه حذف میشود و سپس سند جدیدی بجای آن درج خواهد شد. در اینجا باید دقت داشت که چون Id ثبت شده از نوع StringField است، نباید حالت lower-case آنرا جستجو کرد و باید دقیقا به همان نحوی که ثبت شده، جستجو شود.
6) روش کار با searcherManager جدید لوسین
همانطور که عنوان شد، لوسین جدید به همراه یک searcherManager هم هست که کار آن، ارائهی thread-safe دسترسی به خوانندهی ایندکس لوسین است. نحوهی عمومی کار با آن را در ادامه مشاهده میکنید:
private TResult DoSearch<TResult>(Func<IndexSearcher, TResult> action, TResult defaultValue) { _searcherManager.MaybeRefreshBlocking(); var indexSearcher = _searcherManager.Acquire(); try { return action(indexSearcher); } catch (FileNotFoundException) { // It's not indexed yet. return defaultValue; } finally { _searcherManager.Release(indexSearcher); } }
با استفاده از searcherManager، در طول مدت زمان کوتاهی، بر روی ایندکس قفلگذاری شده و یک indexSearcher امن، در اختیار متدهای استفاده کنندهی از آن قرار میگیرند و در پایان کار، این قفل رها میشود.
برای مثال یک نمونه روش استفاده از این indexSearcher امن، به صورت زیر است:
public int GetNumberOfDocuments() => DoSearch(indexSearcher => indexSearcher.IndexReader.NumDocs, defaultValue: 0);
مابقی مثالهای آنرا میتوانید در کلاس FullTextSearchService مشاهده کنید که به همراه یافتن «مطالب مشابه»، جستجوهای صفحه بندی شده، جستجوهای مرتب شدهی بر اساس یک فیلد، امکان دسترسی به تمام اسناد ذخیره شدهی در ایندکس لوسین و امثال آن است که کلیات آن با قبل تفاوتی نکردهاست و مطالب و نکات آنرا پیشتر در مقالات سری لوسین بررسی کردهایم. تنها تفاوت مهمی که در اینجا وجود دارد، نحوهی برپایی و راه اندازی تحلیلگر، خواننده و نویسندهی ایندکس آن است که در این مطلب بررسی شدند؛ وگرنه کلیات جستجوی پیشرفتهی آن، مانند قبل است و تفاوت خاصی نکردهاست.
پروژه SimpleIdServer
SimpleIdServer is an open source framework enabling the support of OPENID, OAUTH2.0, SCIM2.0, UMA2.0, FAPI and CIBA. It streamlines development, configuration and deployment of custom access control servers. Thanks to its modularity and extensibility, SimpleIdServer can be customized to the specific needs of your organization for authentication, authorization and more.
کتابخانه jquery-sortable
jQuery Sortable is a flexible, opinionated sorting plugin. It enables items in a list (or table etc.) to be sorted horizontally and vertically using the mouse. Supports nested lists and pure drag/drop containers. Demo
jQuery Sortable does not depend on jQuery UI and works well with Twitter's Bootstrap (You can even sort the Bootstrap navigation).
IHostingEnvironment
is one of the most annoying interfaces in .NET Core 2.x, because it exists in two different namespaces, Microsoft.AspNetCore.Hosting and Microsoft.Extensions.Hosting . These are slightly different and are incompatible - one does not inherit from the other. Feature Toggle
"Feature Toggling" is a set of patterns which can help a team to deliver new functionality to users rapidly but safely
public interface IFeatureToggle { bool FeatureEnabled {get;} }
class ShowMessageToggle : IFeatureToggle
{
public bool FeatureEnabled {
get{
return bool.Parse(ConfigurationManager.AppSettings["ShowMessageEnabled"]);
}
}
class Program { static void Main(string[] args) { var toggle = new ShowMessageToggle(); if (toggle.FeatureEnabled) { Console.WriteLine("This feature is enabled") } else { Console.WriteLine("This feature is disabled"); } } }
Install-Package FeatureToggle
MyAwesomeFeature : SimpleFeatureToggle {}
<add key="MyAwesomeFeature " value="true" />
if (!myAwesomeFeature.FeatureEnabled) { // code to disable stuff (e.g. UI buttons, etc) }
dotnet 8 منتشر شد
We are happy to announce the availability of .NET 8, the latest LTS version of one of the world’s leading development platforms, starting today. .NET 8 delivers thousands of performance, stability, and security improvements, as well as platform and tooling enhancements that help increase developer productivity and speed of innovation