فکر کنم فقط مرگ من رو از برنامه نویسی جدا کنه.
کتابخانه emergence.js
Emergence.js is a lightweight, high-performance JS plugin for detecting and manipulating elements in the browser. Demo
- Dependancy-free
- IE8+ and all modern browsers
- 1KB minified and gzipped
npm install emergence.js
bower install emergence.js
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 در مباحث مطرح شده در مطلب جاری، بنا به نیاز خود از این واسطها و کلاسهای پایه پیاده ساز آنها میتوانید استفاده کنید.
مایکروسافت و اپنسورس
OneGet در Windows 10
using System; using System.Diagnostics; using System.IO; using System.Management; using Microsoft.Win32; namespace PdfFilePrinter { /// <summary> /// Executes the Adobe Reader and prints a file while suppressing the Acrobat print /// dialog box, then terminating the Reader. /// </summary> public class AcroPrint { /// <summary> /// The Adobe Reader or Adobe Acrobat path such as 'C:\Program Files\Adobe\Adobe Reader X\AcroRd32.exe'. /// If it's not specified, the InstalledAdobeReaderPath property value will be used. /// </summary> public string AdobeReaderPath { set; get; } /// <summary> /// Returns the default printer name. /// </summary> public string DefaultPrinterName { get { var query = new ObjectQuery("SELECT * FROM Win32_Printer"); using (var searcher = new ManagementObjectSearcher(query)) { foreach (var mo in searcher.Get()) { if (((bool?)mo["Default"]) ?? false) return mo["Name"] as string; } } return string.Empty; } } /// <summary> /// The name and path of the PDF file to print. /// </summary> public string PdfFilePath { set; get; } /// <summary> /// Name of the printer such as '\\PrintServer\HP LaserJet'. /// If it's not specified, the DefaultPrinterName property value will be used. /// </summary> public string PrinterName { set; get; } /// <summary> /// Returns the HKEY_CLASSES_ROOT\Software\Adobe\Acrobat\Exe value. /// If AcroRd32.exe does not exist, returns string.Empty /// </summary> public string InstalledAdobeReaderPath { get { var acroRd32Exe = Registry.ClassesRoot.OpenSubKey(@"Software\Adobe\Acrobat\Exe", writable: false); if (acroRd32Exe == null) return string.Empty; var exePath = acroRd32Exe.GetValue(string.Empty) as string; if (string.IsNullOrEmpty(exePath)) return string.Empty; exePath = exePath.Trim(new[] { '"' }); return File.Exists(exePath) ? exePath : string.Empty; } } /// <summary> /// Executes the Adobe Reader and prints a file while suppressing the Acrobat print /// dialog box, then terminating the Reader. /// </summary> /// <param name="timeout">The amount of time, in milliseconds, to wait for the associated process to exit. The maximum is the largest possible value of a 32-bit integer, which represents infinity to the operating system.</param> public void PrintPdfFile(int timeout = Int32.MaxValue) { if (!File.Exists(PdfFilePath)) throw new ArgumentException(PdfFilePath + " does not exist."); var args = string.Format("/N /T \"{0}\" \"{1}\"", PdfFilePath, getPrinterName()); var process = startAdobeProcess(args); if (!process.WaitForExit(timeout)) process.Kill(); } private Process startAdobeProcess(string arguments = "") { var startInfo = new ProcessStartInfo { FileName = this.getExePath(), Arguments = arguments, CreateNoWindow = true, ErrorDialog = false, UseShellExecute = false, Verb = "print" }; return Process.Start(startInfo); } private string getPrinterName() { var printer = PrinterName; if (string.IsNullOrEmpty(printer)) printer = DefaultPrinterName; if (string.IsNullOrEmpty(printer)) throw new ArgumentException("Please set the PrinterName."); return printer; } private string getExePath() { var exePath = AdobeReaderPath; if (string.IsNullOrEmpty(exePath) || !File.Exists(exePath)) exePath = InstalledAdobeReaderPath; if (string.IsNullOrEmpty(exePath)) throw new ArgumentException("Please set the full path of the AcroRd32.exe or Acrobat.exe."); return exePath; } } }
توضیحات:
استفاده ابتدایی از کلاس فوق به نحو زیر است:
new AcroPrint { PdfFilePath = @"D:\path\test.pdf" }.PrintPdfFile();
ملاحظات:
- کدهای فوق نیاز به ارجاعی به اسمبلی استاندارد System.Management.dll نیز دارند.
- اگر علاقمند بودید که چاپگر خاصی را معرفی کنید (برای مثال یک چاپگر تعریف شده در شبکه)، میتوانید خاصیت PrinterName را مقدار دهی نمائید.
- محل نصب Adobe reader از رجیستری ویندوز استخراج میشود. اما اگر محل نصب برنامه استاندارد نبود، نیاز است خاصیت AdobeReaderPath مقدار دهی گردد.
- تحت هر شرایطی برنامه Adobe reader ظاهر خواهد شد؛ حتی اگر در حین آغاز پروسه سعی در مخفی کردن پنجره آن نمائید. اینکار به عمد جهت مسایل امنیتی در این برنامه درنظر گرفته شده است تا کاربر بداند که پروسه چاپ آغاز شده است.
When I tell my fellow computer scientists or software developers that I'm interested in philosophy of science, they first look a bit confused, then we have a really interesting discussion about it and then they ask me for some interesting books they could read about it. Given that Christmas is just around the corner and some of the readers might still be looking for a good present to get, I thought that now is the perfect time to turn my answer into a blog post!