نحوهی ذخیره سازی لاگها
سطح سایت
- Client IP address
- User name
- Date
- Time
- Service and instance
- Server name
- Server IP address
- Time taken
- Client bytes sent
- Server bytes sent
- (Service status code (A value of 200 indicates that the request was fulfilled successfully
- (Windows status code (A value of 0 indicates that the request was fulfilled successfully.
- Request type
- Target of operation
- (Parameters (the parameters that are passed to a script
192.168.114.201, -, 03/20/01, 7:55:20, W3SVC2, SERVER, 172.21.13.45, 4502, 163, 3223, 200, 0, GET, /DeptLogo.gif, -,
نام فیلد | نوع حالت مقداردهی | توضیح اتفاقات افتاده | |
Client IP address | 192.168.114.201 | آی پی کلاینت | |
User name | - | کاربر ناشناس است | |
Date | 03/20/01 | تاریخ فعالیت | |
Time | 7:55:20 | ساعت فعالیت | |
Service and instance | W3SVC2 | لاگی که مربوط به سایت خاصی میشود به صورت #W3SVC نمایش داده میشود که علامت # شماره سایت میباشد که در اینجا این لاگ مربوط به سایت شماره 2 است | |
Server name | SERVER | نام سرور | |
Server IP | 172.21.13.45 | آی پی سرور | |
Time taken | 4502 | چقدر انجام عملیات این درخواست به طول انجامیده است که بر حسب میلی ثانیه است. | |
Client bytes sent | 163 | تعداد بایت هایی که از طرف کلاینت به سرور ارسال شده است | |
Server bytes sent | 3223 | تعداد بایت هایی که از طرف سرور به سمت کلاینت ارسال شده است | |
Service status code | 200 | درخواست کاملا موفقیت آمیز بوده است | |
Windows status code | 0 | درخواست کاملا موفقیت آمیز بوده است | |
Request type | GET | نوع درخواست کاربر | |
Target of operation | /DeptLogo.gif | کاربر قصد دانلود یک فایل تصویری GIF داشته است که نامش Deptlogo است | |
Parameters | - | پارامتری ارسال نشده است |
فرمت NCSA: این فرمت توسط مرکز علمی کاربردهای ابرمحاسباتی National Center for Supercomputing Applications ایجاد شده و دقیقا مانند قبلی نمیتوان در آن نوع فیلدها را مشخص کرد و برای جدا سازی، از فاصله space استفاده میکند و ثبت مقدار زمان در آن هم به صورت محلی و هم UTC میباشد.
- Remote host address
- (Remote log name (This value is always a hyphen
- User name
- Date, time, and Greenwich mean time (GMT) offset
- Request and protocol version
- (Service status code (A value of 200 indicates that the request was fulfilled successfully
- Bytes sen
نمونه ای از یک لاگ ثبت شده:
172.21.13.45 - Microsoft\JohnDoe [08/Apr/2001:17:39:04 -0800] "GET /scripts/iisadmin/ism.dll?http/serv HTTP/1.0" 200 3401
نام فیلد | مقدار ثبت شده | توضیح اتفاق افتاده |
Remote host address | 172.21.13.45 | آی پی کلاینت |
Remote log name | - | نامی وجود ندارد |
User name | Microsoft\JohnDoe | نام کاربری |
Date, time, and GMT offset | [08/Apr/2001:17:39:04 -0800] | تاریخ و ساعت فعالیت به صورت محلی که 8 ساعت از مبدا گرینویچ بیشتر است |
Request and protocol version | GET /scripts/iisadmin/ism.dll?http/serv HTTP/1.0 | کاربر با متد GET و Http نسخهی یک، درخواست فایل ism.dll را کرده است. |
Service status code | 200 | عملیات کاملا موفقیت آمیز بود. |
Bytes sent | 3401 | تعداد بایتهای ارسال شده به سمت کاربر |
امنیت در برابر کاربران مهاجم مانند همان فرمت قبلی صورت گرفته است.
فرمت W3C: توسط W3C توسط کنسرسیوم جهانی وب ارائه شده است و یک فرمت customizable ASCII text-based است. به این معنی که میتوان فیلدهایی که در گزارش نهایی میآید را خودتان مشخص کنید، که برای اینکار در کنار لیست، دکمهی Select وجود دارد که میتوانید هر کدام از فیلدهایی را که خواستید، انتخاب کنید تا به ترتیب در خط لاگ ظاهر شوند. تاریخ ثبت به صورت UTC است.
نام فیلد | توضیح | به طور پیش فرض انتخاب شده است |
Date | تاریخ رخ دادن فعالیت | بله |
Time | ساعت زخ دادن فعالیت بر اساس UTC | بله |
Client IP Address | آی پی کلاینت | بله |
User Name | نام کاربری که هویت آن تایید شده و در صورتی که هویت تایید شده نباشد و کاربر ناشناس باشد، جای آن - قرار میگیرد | بله |
Service Name and Instance Number | نام و شماره سایتی که درخواست در آن صورت گرفته است | خیر |
Server Name | نام سروری که لاگ روی آن ثبت میشود | خیر |
Server IP Address | آی پی سرور که لاگ روی آن ثبت میشود | بله |
Server Port | شمره پورتی که سرویس مورد نظر روی آن پورت اعمال میشود. | بله |
Method | متد درخواست مثل GET | بله |
URI Stem | هدف درخواست یا Target مثل index.htm | بله |
URI Query | کوئری ارسال شده برای صفحات داینامیک | بله |
HTTP Status | کد وضیعینی HTTP status | بله |
Win32 Status | کد وضعیتی ویندوز | خیر |
Bytes Sent | تعداد بایتهای ارسال شده به سمت کلاینت | خیر |
Bytes Received | تعداد بایتهای دریافت شده از سمت کلاینت | خیر |
Time Taken | زمان به طول انجامیدن درخواست بر حسب میلی ثانیه | خیر |
Protocol Version | درخواست با چه نسخهای از پروتکل http یا ftp ارسال شده است | خیر |
Host | اگر در هدر درخواست ارسالی این گزینه بوده باشد، نوشته خواهد شد. | خیر |
User Agent | اطلاعات را از هدر درخواست میگیرد. | بله |
Cookie | اگر کوکی رد و بدل شده باشد، محتویات کوکی ارسالی یا دریافت شده | خیر |
Referrer | کاربر از چه سایتی به سمت سایت ما آمده است. | خیر |
Protocol Substatus | در صورت رخ دادن خطا در IIS ، کد خطا بازگردانده میشود. در IIS به منظور امنیت بیشتر و کاهش حملات، محتوای خطاهای رخ داده در IIS به صورت متنی نمایش داده نمیشوند و شامل کد خطایی به اسم Substatus Code هستند تا مدیران شبکه با ردیابی لاگها پی به دلیل خطا و درخواستهای ناموفق ببرند. برای مثال Error 404.2 به این معنی است که فایل درخواستی به دلیل قوانین محدود کنده، قفل شده و قابل ارائه نیست. ولی هکر تنها با خطای 404 یعنی وجود نداشتن فایل روبرو میشود. در حالت substatus code، کد شماره 2 را هم خواهید داشت که در لاگ ثبت میشود. هر شخصی که در سرور توانایی دسترسی به لاگها را داشته باشد، میتواند کد دوم خطا را نیز مشاهده کند. برای مثال مدیر سرور متوجه میشود که یکی از فایلهای مورد نظر به کاربران، خطای 404 نمایش میدهد و با بررسی لاگها متوجه میشود که کد خطا 404.9 هست. از آنجا که ما همهی کدها را حفظ نیستیم به این صفحه رجوع میکنیم و متوجه میشویم تعداد کاربرانی که برای این فایل، اتصال connection ایجاد کردهاند بیش از مقدار مجاز است و مدیر میتواند این وضع را کنترل کند. برای مثال تعداد اتصالات مجاز را نامحدود unlimited تعیین کند. | بله |
- uri-query
- host
- (User-Agent)
- Cookie
- Referrer
- substatus
گزینه Custom : موقعی که شما این گزینه را انتخاب کنید ماژول logging غیرفعال خواهد شد. زیرا این امکان در IIS قابل پیکربندی نیست و نوشتن ماژول آن بر عهده شما خواهد بود؛ با استفاده از اینترفیس های ILogPlugin ، ILogPluginEx و ILogUIPlugin آن را پیاده سازی کنید.
ذخیره اطلاعات به انکدینگ UTF-8 و موضوع امنیت
روش متداول ایجاد کارهای پس زمینه
سادهترین روش انجام کارهای پس زمینه در برنامههای دات نتی، استفاده از متدهایی هستند که یک ترد جدید را ایجاد میکنند مانند Task.Run, Task.Factory.StartNew, Delegate.BeginInvoke, ThreadPool.QueueUserWorkItem و امثال آن. اما ... این روشها در برنامههای ASP.NET ایدهی خوبی نیستند!
از این جهت که موتور ASP.NET در این حالات اصلا نمیداند که شما کار پس زمینهای را ایجاد کردهاید. به همین جهت اگر پروسهی برنامه پس از مدتی recycle شود، تمام کارهای پس زمینهی موجود نیز از بین خواهند رفت.
معرفی HostingEnvironment.QueueBackgroundWorkItem
متد HostingEnvironment.QueueBackgroundWorkItem به دات نت 4.5.2 اضافه شدهاست تا بتوان توسط آن یک کار پس زمینه را توسط موتور ASP.NET شروع کرد و نه مانند قبل، بدون اطلاع آن. البته باید دقت داشت که این کارهای پس زمینه مستقل از هر نوع درخواستی اجرا میشوند.
در این حالت چون موتور ASP.NET از وجود کار پس زمینهی آغاز شده مطلع است، در صورت فرا رسیدن زمان recycle شدن برنامه، کل AppDomain را به یکباره نابود نخواهد کرد. البته این مورد فقط به این معنا است که در صورت فرا رسیدن زمان recycle شدن پروسه، با تنظیم یک CancellationToken، اطلاع رسانی خواهد کرد. در این حالت حداکثر 30 ثانیه فرصت خواهید داشت تا کارهای پس زمینه را بدون مشکل خاتمه دهید. اگر کار پس زمینه در این مدت به پایان نرسد، همانند قبل، کل AppDomain نابود خواهد شد.
این متد دو overload دارد و در هر دو حالت، تنظیم خودکار پارامتر CancellationToken توسط ASP.NET، بیانگر آغاز زمان خاتمهی کل برنامه است:
public static void QueueBackgroundWorkItem(Action<CancellationToken> workItem); public static void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
علت استفاده از Action و Func در اینجا، امکان تعریف خلاصه و inline یک متد و ارسال پارامتری به آن از طرف برنامه است، بجای تعریف یک اینترفیس جدید، نیاز به پیاده سازی آن اینترفیس و بعد برای مثال ارسال یک مقدار از طرف برنامه به متد Stop آن (بجای تعریف یک اینترفیس تک متدی، از Action و یا Func نیز میتوان استفاده کرد).
نمونهای از نحوهی فراخوانی این دو overload را در ذیل مشاهده میکنید:
HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => { //todo: ... }); HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => { //todo: ... await Task.Delay(20000, cancellationToken); });
پشت صحنهی HostingEnvironment.QueueBackgroundWorkItem
روش استاندارد ثبت و معرفی یک کار پس زمینه در ASP.NET، توسط پیاده سازی اینترفیسی به نام IRegisteredObject انجام میشود. سپس توسط متد HostingEnvironment.RegisterObject میتوان این کلاس را به موتور ASP.NET معرفی کرد. در این حالت زمانیکه AppDomain قرار است خاتمه یابد، متد Stop اینترفیس IRegisteredObject کار اطلاع رسانی را انجام میدهد. توسط QueueBackgroundWorkItem دقیقا از همین روش به همراه فراخوانی ThreadPool.QueueUserWorkItemجهت اجرای متد معرفی شدهی به آن استفاده میشود.
از مکانیزم IRegisteredObject در DNT Scheduler نیز استفاده شدهاست.
پیشنیازها
ابتدا نیاز است به خواص پروژه مراجعه کرده و Target framework را بر روی 5.4.2 قرار داد. اگر به روز رسانی دوم VS 2013 را نصب کرده باشید، این نگارش هم اکنون بر روی سیستم شما فعال است. اگر خیر، امکان دریافت و نصب آن، به صورت جداگانه نیز وجود دارد:
.NET Framework 4.5.2
Developer pack
محدودیتهای QueueBackgroundWorkItem
- از آن در خارج از یک برنامهی وب ASP.NET نمیتوان استفاده کرد.
- توسط آن، خاتمهی یک AppDomain تنها به مدت 30 ثانیه به تاخیر میافتد؛ تا فرصت داشته باشید کارهای در حال اجرا را با حداقل خسارت به پایان برسانید.
- یک work item، اطلاعاتی را از فراخوان خود دریافت نمیکند. به این معنا که مستقل از زمینهی یک درخواست اجرا میشود.
- استفادهی از آن الزاما به این معنا نیست که کار درخواستی شما حتما اجرا خواهد شد. زمانیکه که کار خاتمهی AppDomain آغاز میشود، فراخوانیهای QueueBackgroundWorkItem دیگر پردازش نخواهند شد.
- اگر برنامه به مقدار CancellationToken تنظیم شده توسط ASP.NET دقت نکند، جهت پایان یافتن کار در حال اجرا، صبر نخواهد شد.
خواندنیهای 4 شهریور
آفیس
اس کیوال سرور
الگوهای طراحی برنامه نویسی شیءگرا
توسعه وب
دات نت فریم ورک
متفرقه
محیطهای مجتمع توسعه
مرورگرها
ویندوز
PM> Install-Package DNTFrameworkCore -Version 1.0.0
مثال اول: یک موجودیت ساده بدون نیاز به مباحث ردیابی تغییرات
public class MeasurementUnit : Entity<int>, IAggregateRoot { public const int MaxTitleLength = 50; public const int MaxSymbolLength = 50; public string Title { get; set; } public string NormalizedTitle { get; set; } public string Symbol { get; set; } public byte[] RowVersion { get; set; } }
کلاس جنریک Entity، در برگیرنده یکسری اعضای مشترک بین سایر موجودیتهای سیستم از جمله Id و TrackingState (به منظور سناریوهای Master-Detail)، میباشد.
نکته: در این زیرساخت برای پیاده سازی CrudService برای یک موجودیت خاص، نیاز است تا واسط IAggregateRoot را نیز پیاده سازی کرده باشد. برای پیاده سازی واسط مذکور نیاز است تا خصوصیت RowVersion را به منظور مدیریت Optimistic مباحث همزمانی، به کلاس بالا اضافه کنیم. این موضوع برای موجودیتهای وابسته به یک Aggregate ضروری نیست، چرا که آنها با AggregateRoot ذخیره خواهند شد و تراکنش جدایی برای ثبت، ویرایش و یا حذف آنها وجود ندارد.
مثال دوم: یک موجودیت به همراه مباحث ردیابی تغییرات ثبت و آخرین ویرایش
public class Blog : TrackableEntity<long>, IAggregateRoot { public const int MaxTitleLength = 50; public const int MaxUrlLength = 50; public string Title { get; set; } public string NormalizedTitle { get; set; } public string Url { get; set; } public byte[] RowVersion { get; set; } }
کلاس جنریک TrackableEntity علاوه بر خصوصیات Id و TrackingState، یکسری خصوصیت دیگر از جمله زمان ثبت، زمان آخرین ویرایش، شناسه کاربر ثبت کننده، شناسه آخرین کاربر ویرایش کننده، اطلاعات مرورگرهای آنها و ... را نیز دارا میباشد. این خصوصیات به صورت خودکار توسط زیرساخت مقداردهی خواهند شد.
مثال سوم: یک موجودیت به همراه مباحث ردیابی تغییرات ثبت، آخرین ویرایش و حذف نرم
public class Blog : FullTrackableEntity<long>, IAggregateRoot { public const int MaxTitleLength = 50; public const int MaxUrlLength = 50; public string Title { get; set; } public string NormalizedTitle { get; set; } public string Url { get; set; } public byte[] RowVersion { get; set; } }
کلاس جنریک FullTrackableEntity علاوه بر خصوصیات ذکر شده در مثال دوم، یکسری خصوصیت دیگر از جمله IsDeleted، شناسه کاربر حذف کننده، زمان حذف و ... را نیز دارا میباشد. همچنین مباحث فیلتر خودکار رکوردهای حذف شده، به صورت خودکار توسط زیرساخت انجام میگیرد که امکان غیرفعال کردن آن در شرایط مورد نیاز نیز وجود دارد.
مثال چهارم: یک موجودیت با پشتیبانی از چند مستاجری
public class Blog : Entity<long>, IAggregateRoot, ITenantEntity { public const int MaxTitleLength = 50; public const int MaxUrlLength = 50; public string Title { get; set; } public string NormalizedTitle { get; set; } public string Url { get; set; } public byte[] RowVersion { get; set; } public long TenantId { get; set; } }
با پیاده سازی واسط ITenantEntity، به صورت خودکار خصوصیت TenantId آن با توجه به اطلاعات مستاجر جاری سیستم مقداردهی خواهد شد و همچنین فیلتر خودکار بر روی رکوردهای مستاجرهای مختلف، توسط زیرساخت انجام میشود که این مکانیزم هم قابلیت غیرفعال شدن در شرایط خاص را دارد.
مثال پنجم: یک موجودیت به همراه تعدادی موجودیت جزئی (سناریوهای Master-Detail)
public class Invoice : TrackableEntity<long>, IAggregateRoot { public InvoiceStatus Status { get; set; } public decimal TotalNet { get; set; } public decimal Total { get; set; } public decimal PayableTotal { get; set; } public decimal Debit { get; set; } public decimal Credit { get; set; } public decimal Gratuity { get; set; } public byte[] RowVersion { get; set; } public ICollection<InvoiceItem> Items { get; set; } } public class InvoiceItem : TrackableEntity { public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal Price { get; set; } public decimal UnitPriceDiscount { get; set; } public long ItemId { get; set; } public Item Item { get; set; } public long InvoiceId { get; set; } public Invoice Invoice { get; set; } }
همانطور که مشخص میباشد، موجودیت وابسته یا همان Detail، نیاز به پیاده سازی IAggregateRoot را نخواهد داشت. همانطور که اشاره شد، تراکنش مجزایی برای این موجودیتها نخواهیم داشت و درون تراکنش AggregateRoot، عملیات CRUD آنها انجام خواهد شد و برای انجام عملیات ویرایش، به همراه Root متناظر با خود، واکشی خواهند شد. این موضوع یکی از نقاط قوت زیرساخت محسوب میشود که در مقالات آینده و در قسمت طراحی سرویسهای متناظر با موجودیتهای سیستم، با جزئیات بیشتری بررسی خواهد شد.
مثال ششم: یک موجودیت با امکان شماره گذاری خودکار
public class Task : TrackableEntity, IAggregateRoot, INumberedEntity { public const int MaxTitleLength = 256; public const int MaxDescriptionLength = 1024; public string Title { get; set; } public string NormalizedTitle { get; set; } public string Number { get; set; } public string Description { get; set; } public TaskState State { get; set; } = TaskState.Todo; public byte[] RowVersion { get; set; } }
همانطور که در مطلب «طراحی و پیاده سازی زیرساختی برای تولید خودکار کد منحصر به فرد در زمان ثبت رکورد جدید» ملاحظه کردید، نیاز است تا موجودیت مورد نظر، پیاده ساز واسط INumberedEntity نیز باشد. این واسط دارای خصوصیت رشتهای Number میباشد و همچنین زیرساخت به صورت خودکار در زمان ثبت، این خصوصیت را برای موجودیتهایی از این نوع، با رعایت مباحث همزمانی مقداردهی میکند.
مثال هفتم: یک موجودیت با امکان ذخیره سازی اطلاعات اضافی در قالب فیلد JSON
public class Task : TrackableEntity, IAggregateRoot, INumberedEntity, IExtendableEntity { public const int MaxTitleLength = 256; public const int MaxDescriptionLength = 1024; public string Title { get; set; } public string NormalizedTitle { get; set; } public string Number { get; set; } public string Description { get; set; } public TaskState State { get; set; } = TaskState.Todo; public byte[] RowVersion { get; set; } public string ExtensionJson { get; set; } }
با پیاده سازی واسط IExtendableEntity، یکسری متد الحاقی برروی اشیاء موجودیت مورد نظر فعال خواهند شد که امکان مقداردهی یا خواندن این اطلاعات اضافی را خواهید داشت. به عنوان مثال:
var task = new Task(); task.SetExtensionValue("Name","Value"); var value = task.ReadExtensionValue("Name"); //or any complex object as string json
با دو متد الحاقی استفاده شده در بالا، امکان مقداردهی، تغییر و خواندن مقدار خصوصیتهای اضافی را خواهیم داشت که نیاز است موجودیت مورد نظر در دل خود نگهداری کند ولی ارزش و اهمیت زیادی در Domain ندارند.
مثال هشتم: طراحی یک نوع شمارشی (Enum)
public class OrderStatus : Enumeration { public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant()); public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant()); public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant()); public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant()); public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant()); public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant()); protected OrderStatus() { } public OrderStatus(int id, string name) : base(id, name) { } }
برای سناریوهایی که صرفا قصد انتخاب یک یا چند (حالت enum flags) مورد از بین یک لیست مشخص و سپس ذخیره سازی آنها را دارید، استفاده از نوع داده enum کفایت میکند؛ ولی اگر قصد استفاده از آنها برای flow control را دارید، در این صورت به طراحی شکنندهای خواهید رسید که پر شده است از if/else هایی که مقادیر مختلف enum مورد نظر را بررسی میکنند. با استفاده از کلاس Enumeration امکان مدل کردن انوع شمارشی که مرتبط هستند با منطق تجاری سیستم را با راه حل شیء گرا خواهید داشت. در این صورت رفتارهای متناظر با هریک از فیلدهای یک نوع شمارشی میتواند به عنوان رفتاری در دل خود کپسوله شده باشد و اینبار داده و رفتار کنار هم خواهند بود.
نکته: برای مطالعه بیشتر میتوانید به مطالب ^ و ^ مراجعه کنید.
در نهایت میتوانید برای سناریوهای خاص خودتان از سایر واسط های موجود در زیرساخت، نیز به شکل زیر استفاده کنید:
نیاز به حذف نرم بدون نگهداری اطلاعات ردیابی تغییرات
public interface ISoftDeleteEntity { bool IsDeleted { get; set; } }
.با پیاده سازی واسط بالا این امکان را خواهید داشت که صرفا از مکانیزم حذف نرم استفاده کنید؛ بدون نیاز به نگهداری سایر اطلاعات
نیاز به مقداردهی خودکار زمان ثبت یک موجودیت خاص
این امر با پیاده سازی واسط زیر امکان پذیر خواهد بود.
public interface IHasCreationDateTime { DateTimeOffset CreationDateTime { get; set; } }
با توجه به اعمال اصل ISP در مباحث مطرح شده در مطلب جاری، بنا به نیاز خود از این واسطها و کلاسهای پایه پیاده ساز آنها میتوانید استفاده کنید.
ابزار خاصی که حملهی فوق را بر روی خیلی از سایتها اجرا کردهاست، هدرهای اضافی «HTTP_X_NEWRELIC_TRANSACTION و HTTP_X_NEWRELIC_ID» را هم ارسال میکند (مطابق تصویر) و برای شناسایی آن مفید است (یک HTTP Module را میتوانید برای آن طراحی کنید که BeginRequest را تحت نظر قرار دهد):
foreach (string item in HttpContext.Current.Request.Headers) { if (string.IsNullOrWhiteSpace(item)) continue; if (item == "HTTP_X_NEWRELIC_TRANSACTION" || item == "HTTP_X_NEWRELIC_ID") { HttpContext.Current.Response.StatusCode = 403; //Forbidden HttpContext.Current.Response.End(); } }
گالری تصاویر تحت وب با افکتهای زیبا
آموزش PlayFramework
مکانیزم Eventing
PM> Install-Package DNTFrameworkCore
public class TaskEditingBusinessEventHandler : BusinessEventHandler<EditingBusinessEvent<TaskModel, int>> { private readonly ILogger<TaskEditingBusinessEventHandler> _logger; public TaskEditingBusinessEventHandler(ILogger<TaskEditingBusinessEventHandler> logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public override Task<Result> Handle(EditingBusinessEvent<TaskModel, int> @event) { foreach (var model in @event.Models) { _logger.LogInformation($"Title changed from: {model.OriginalValue.Title} to: {model.NewValue.Title}"); } return Task.FromResult(Ok()); } }
کار با پیادهسازی واسط جنریک IBusinessEventHandler یا ارثبری از کلاس جنریک BusinessEventHandler آغاز میشود؛ سپس نیاز است Type Parameter متناظر را نیز مشخص کنیم. برای این منظور در تکه کد بالا از رخداد جنریک EditingBusinessEvent استفاده شده است. همچنین همانطور که ملاحظه میکنید، نیاز است نوع Model مورد نظر نیز مشخص شده باشد؛ در اینجا از TaskModel به عنوان Model/DTO عملیات CUD موجودیت Task استفاده شده است.
رخدادهای Creating/Created/Deleting/Deleted دارای خصوصیتی بنام Models هستند که نوع آن IEnumerable<TModel> میباشد. ولی این خصوصیت در رخدادهای Editing/Edited از نوع IEnumerable<ModifiedModel<TModel>> میباشد؛ در این صورت به مقادیر موجود در بانک اطلاعاتی و همچنین مقادیری که توسط استفاده کننده از سرویس جاری به عنوان آرگومان به متد ویرایش ارسال شده است، دسترسی خواهیم داشت.
public class ModifiedModel<TValue> { public TValue NewValue { get; set; } public TValue OriginalValue { get; set; } }
استفاده از سرویسهای موجودیتها
OOP : Everything is an object CRUD-based thinking : Everything is CRUD
public class ItemCategoryCreatedBusinessEventHandler : IBusinessEventHandler<CreatedBusinessEvent<ItemCategoryModel, int>> { private readonly ISaleMethodService _saleMethodService; public TaskEditingBusinessEventHandler(ISaleMethodService saleMethodService) { _saleMethodService = saleMethodService ?? throw new ArgumentNullException(nameof(saleMethodService)); } public override Task<Result> Handle(CreatedBusinessEvent<ItemCategoryModel, int> @event) { var methods = _saleMethodService.FindAsnc(); foreach (var method in methods) { foreach (var model in @event.Models) { method.ItemCategories.Add(new SaleMethodItemCategoryModel { ItemCategoryId = model.Id, TrackingState = TrackingState.Added; }); } } return _saleMethodService.EditAsync(methods); }