پیاده سازی CQRS توسط MediatR - قسمت پنجم
پیاده سازی CQRS توسط MediatR - قسمت اول
مقدمه ای بر CQRS و Event Sourcing
EF Code First #2
code first اجازه درست کردن trigger رو میده؟
کلا من یه مکانیزمی میخوام که اگه کسی (غیر خودم) تو یه جدول خاص insert کرد با یه چیزی مثل event تو برنامه متوجه بشم.
یک نکتهی تکمیلی: روش لغو صف درخواستهای مکرر fetch ارسالی به سمت سرور
ورودی جستجوی بالای صفحه را درنظر بگیرید که بهازای هربار ورود حرفی، یک درخواست fetch جدید را به سمت سرور ارسال میکند تا نتایج جستجوی حاصل را دریافت کند. مشکل اینجاست که ما تنها به آخرین درخواست fetch ارسال شدهی به سمت سرور نیاز داریم و نه به تمام درخواستهای دیگری که صادر شدهاند. به همین جهت این صف درخواستهای fetch قبلی، غیربهینه بوده و ترافیک بالایی را سبب میشوند. یک روش مواجه شدن با این مساله، استفاده از مفهومی به نام debounce است که در پشت صحنه، از یک تایمر استفاده میکند و فقط هر چند ثانیه یکبار، یک درخواست جدید را به همراه آخرین متن ورودی، به سمت سرور ارسال خواهد کرد. راه دیگری هم برای مواجه شدن با این مشکل، در مرورگرهای جدید پیشبینی شدهاست که AbortController نام دارد. با استفاده از آن میتوان «سیگنالی» را به صف درخواستهای پرتعداد fetch قبلی حاصل از ورود اطلاعات کاربر ارسال کنیم که ... «لغو شوید» و به سمت سرور ارسال نشوید.
برای توضیح بهتر آن، به مثال زیر دقت کنید:
<!DOCTYPE html> <html> <body> <input id="search" type="number" /> <script> const results = []; const search = document.getElementById("search"); let controller = new AbortController(); let signal = controller.signal; const onChange = () => { const value = search.value; if (value) { controller.abort(); controller = new AbortController(); signal = controller.signal; getPost(value, signal); } }; search.onkeyup = onChange; </script> </body> </html>
- در اینجا یک input box را داریم که ابتدا، یافت شده و سپس به رخداد onkeyup آن، متد onChange نسبت داده شدهاست تا هربار که کاربر، اطلاعاتی را وارد میکند، فراخوانی شود.
- در ابتدای اسکریپت هم نحوهی نمونه سازی شیء استاندارد جاوااسکریپتی AbortController و دسترسی به شیء signal آنرا مشاهده میکنید.
- در متد onChange، ابتدا مقدار جدید ورودی کاربر، دریافت میشود، سپس این AbortController، لغو میشود و بعد یک نمونهی جدید از آن ایجاد شده و مجددا به شیء signal آن دسترسی پیدا میکنیم تا آنرا به متد getPost ارسال کنیم. این متد هم چنین پیاده سازی را دارد:
const getPost = (value, signal) => { fetch( `https://site.com/search/${value}`, { signal } ); };
همانطور که مشاهده میکنید، تابع fetch، قابلیت پذیرش شیء signal را هم دارد. زمانیکه با هربار تایپ کاربر، متد ()controller.abort فراخوانی میشود، سیگنالی را به fetch «قبلی» متصل به آن ارسال میکند که ... دیگر به سمت سرور ارسال نشو و متوقف شو. با اینکار فقط آخرین ورودی کاربر، سبب بروز یک fetch موفق میشود و ترافیک ارسالی به سمت سرور کاهش پیدا میکند (چون تمام fetchهای قبلی، سیگنال abort را دریافت کردهاند)؛ مانند مثال زیر که کاربر، 5 بار حروفی را وارد کرده و به ازای هربار ورود حرفی، یک درخواست fetch جدید، ایجاد شده، اما ... فقط آخرین درخواست ارسالی او موفق بوده و نتیجهای را بازگشت داده و مابقی درخواستها ... abort شدهاند. این عملیات abort، در سمت کاربر اعمال میشود؛ یعنی اصلا درخواستی به سمت سرور ارسال نمیشود و این لغو درخواست، توسط برنامهی سمت سرور انجام نشدهاست.
- 12 ساعت قبل
- دیروز
- لحظاتی پیش
- ...
public class RelativeTimeCalculator { const int SECOND = 1; const int MINUTE = 60 * SECOND; const int HOUR = 60 * MINUTE; const int DAY = 24 * HOUR; const int MONTH = 30 * DAY; public static string Calculate(DateTime dateTime) { var ts = new TimeSpan(DateTime.Now.Ticks - dateTime.Ticks); double delta = Math.Abs(ts.TotalSeconds); if (delta < 1 * MINUTE) { return ts.Seconds == 1 ? "لحظه ای قبل" : ts.Seconds + " ثانیه قبل"; } if (delta < 2 * MINUTE) { return "یک دقیقه قبل"; } if (delta < 45 * MINUTE) { return ts.Minutes + " دقیقه قبل"; } if (delta < 90 * MINUTE) { return "یک ساعت قبل"; } if (delta < 24 * HOUR) { return ts.Hours + " ساعت قبل"; } if (delta < 48 * HOUR) { return "دیروز"; } if (delta < 30 * DAY) { return ts.Days + " روز قبل"; } if (delta < 12 * MONTH) { int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30)); return months <= 1 ? "یک ماه قبل" : months + " ماه قبل"; } int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365)); return years <= 1 ? "یک سال قبل" : years + " سال قبل"; } }
var relativeTime=RelativeTimeCalculator.Calculate(DateTime.Now.AddMinutes(-10));
- dotnet-ignore : این ابزار جهت دریافت فایلهای gitignore. کاربرد داشته و از یک مخزن عمومی گیت هاب جهت دریافت این فایلها استفاده میکند. این مخزن شامل انواع قالبهای gitignore در پروژههای متفاوت میباشد. با استفاده از این ابزار، ایجاد فایل gitignore راحتتر و سریعتر امکانپذیر میباشد.
- dotnet-serve : میزبانی و نمایش لیست فایلهای استاتیک محلی و اجرای آنها را در بستر http، فراهم مینماید.
- dotnet-cleanup : جهت پاکسازی محیط بیلد مانند دایرکتوریهای bin و obj میباشد. همان کار گزینه clean در منوی بیلد را بازی میکند.
- dotnet-warp : این ابزار در واقع پروژه Warp است که برای ایجاد یک تک فایل اجرایی جهت انتقال راحتتر فایل پروژه صورت میگیرد که همه وابستگیهای آن در همان تک فایل قرار میگیرد.
- Amazon.ECS.Tools , Amazon.ElasticBeanstalk.Tools و Amazon.Lambda.Tools : این ابزارها که به صورت رسمی از طرف آمازون ارائه شدهاند که جهت deploy شدن راحتتر پروژه به محیطهای توسعه وب آمازون مورد استفاده قرار میگیرند.
dotnet tool install -g dotnet-ignore
dotnet tool list -g
dotnet tool update -g dotnet-ignore
dotnet tool uninstall -g dotnet-ignore
<PropertyGroup> <PackAsTool>true</PackAsTool> <ToolCommandName>dotnet-mytool</ToolCommandName> <PackageOutputPath>./nupkg</PackageOutputPath> </PropertyGroup>
dotnet tool install --global --add-source ./nupkg globaltools
dotnet-mytool
https://www.nuget.org/packages/McMaster.Extensions.CommandLineUtils
[Command(Description="Add a new note")] public class NewNote { [Required] [Option(Description="title of note")] public string Title{ get; set; } [Option(Description="content of note")] public string Body{ get; set; } }
[Command(Description="Add a new note")] public class NewNote:BaseClass { [Required] [Option(Description="title of note")] public string Title{ get; set; } [Option(Description="content of note")] public string Body{ get; set; } public void OnExecute(IConsole console) { var dir = GetBaseDirectory(); if(!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var filePath = Path.Combine(dir, Title + ".txt"); File.WriteAllText(filePath, Body); console.WriteLine("the note is saved"); } }
public class BaseClass { protected string GetBaseDirectory(){ var baseDirectory = Environment.CurrentDirectory; return (Path.Combine(baseDirectory, "notes")); } }
public class List:BaseClass { [Option(Description="search a phrase in notes title")] public string Grep{ get; set; } public void OnExecute(IConsole console) { try { var baseDirectory = GetBaseDirectory(); var dir = new DirectoryInfo(baseDirectory); var files = dir.GetFiles(); foreach(var file in files) { if(!String.IsNullOrEmpty(Grep) && !file.Name.Contains(Grep)) continue; console.WriteLine(Path.GetFileNameWithoutExtension(file.Name)); } } catch (Exception e) { console.WriteLine(e.Message); } } }
[Command(Description="show contnet of note")] public class Show:BaseClass { [Required] [Option(Description="title of note")] public string Title{ get; set; } public void OnExecute(IConsole console){ var baseDirectory = GetBaseDirectory(); var file = Path.Combine(baseDirectory, Title+".txt"); if(!File.Exists(file)) { console.WriteLine("The Note NotFound..."); return; } console.WriteLine(File.ReadAllText(file)); } }
[Command(Description="An Immediate Note Saver")] [Subcommand(typeof(NewNote),typeof(List),typeof(Show))] class Program { static int Main(string[] args) { return CommandLineApplication.Execute<Program>(args); } public int OnExecute(CommandLineApplication app, IConsole console) { console.WriteLine("You must specify a subcommand."); console.WriteLine(); app.ShowHelp(); return 1; } }
static int Main(string[] args) { return CommandLineApplication.Execute<Program>(args); }
PS D:\projects\Samples\globaltools> dotnet-notes new-note -t "sample1" -b "this is body" the note is saved PS D:\projects\Samples\globaltools> dotnet-notes new-note -t "test1" -b "this is body of another note" the note is saved PS D:\projects\Samples\globaltools> dotnet-notes list sample1 test1 PS D:\projects\Samples\globaltools> dotnet-notes list -g sa sample1 PS D:\projects\Samples\globaltools> dotnet-notes show -t sample1 this is body
نوشتن افزونه برای مرورگرها: قسمت اول : کروم
اگر از IISهای جدید استفاده میکنید، میتوان فیلتر عنوان شدهی در این بحث را حذف و از ماژول Url Rewrite خود IIS استفاده کرد.
الگویی که در بسیاری از سایتها در مورد آن بحث شدهاست به صورت زیر است:
<system.webServer> <rewrite> <rules> <rule name="Enforce WWW" patternSyntax="ECMAScript" stopProcessing="true"> <match url=".*" /> <conditions> <add input="{CACHE_URL}" pattern="^(.+):\/\/(?!www\.)(.*)" /> </conditions> <action type="Redirect" url="{C:1}://www.{C:2}" redirectType="Permanent" /> </rule> </rules> </rewrite> </system.webServer>
HTTP Error 500.50 - URL Rewrite Module Error. The expression "{C:1}://www.{C:2}" cannot be expanded.
<rule name="Enforce WWW" patternSyntax="ECMAScript" stopProcessing="true"> <match url=".*" /> <conditions> <add input="{CACHE_URL}" pattern="^(.+):\/\/(?!www\.)(.*)" /> <add input="{HTTP_HOST}" pattern="localhost" negate="true" /> </conditions> <action type="Redirect" url="https://www.dntips.ir/{R:0}" redirectType="Permanent" /> </rule>
به علاوه باید دقت داشت که ماژول Url rewrite به صورت پیش فرض نصب نیست و باید از طریق web platform installer نصب شود.
HTTP Error 500.19 - Internal Server Error Error Code 0x8007000d