- در اینجا ابتدا فضای نام اصلی پروژهی جاری ذکر شدهاست. به همین جهت است که عنوان کردهاند اگر منابع در اسمبلی جاری هستند، نباید مجددا این فضای نام پیش فرض ذکر شود.
- سپس نام Resources را مشاهده میکنید. بنابراین چیزی که بارگذاری میشود، یک منبع مدفون شدهی در یک فایل dll است و نه اینکه در زمان اجرا به پوشهی Resources مراجعه میشود. این پوشهی Resources در اینجا در حد یک جزء از نام کامل بیشتر مطرح نیست.
- در آخر هم نام کامل نوع مدنظر ذکر شدهاست. مثلا نام کامل ViewModel مورد استفاده.
- این نکات در مورد فایلهای Shared هم مطرح هستند.
- من چون نام پوشهی Views را به Features تغییر دادهام، دومین فایل لیست شدهی در اینجا، چنین نامی را دارد (بجای Views استاندارد).
بنابراین اگر اطلاعات را به اسمبلیهای دیگر منتقل میکنید:
- به ذکر کامل فضاهای نام دقت داشته باشید.
- بررسی کنید آیا اسمبلی منبع با اسمی شبیه به Core1RtmEmptyTest.resources.dll در پوشهی bin\Debug\netcoreapp1.0\fa موجود هست یا خیر؟ و اگر موجود است، بررسی کنید چه محتوایی در آن ثبت شدهاست.
columns: [ { field: "IsAvailable", title: "موجود است", template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>' } ]
همانطور که در این مثال نیز مشاهده میکنید، قالبهای Kendo UI از Hash (#) syntax استفاده میکنند. در اینجا قسمتهایی از قالب که با علامت # محصور میشوند، در حین اجرا، با اطلاعات فراهم شده جایگزین خواهند شد.
برای رندر مقادیر ساده میتوان از # =# استفاده کرد. از # :# برای رندر اطلاعات HTML-encoded کمک گرفته میشود و # # برای رندر کدهای جاوا اسکریپتی کاربرد دارد. از حالت HTML-encoded برای نمایش امن اطلاعات دریافتی از کاربران و جلوگیری از حملات XSS استفاده میشود.
اگر در این بین نیاز است # به صورت معمولی رندر شود، در حالت کدهای جاوا اسکریپتی به صورت #\\ و در HTML ساده به صورت #\ باید مشخص گردد.
مثالی از نحوهی تعریف یک قالب Kendo UI
<!--دریافت اطلاعات از منبع محلی--> <script id="javascriptTemplate" type="text/x-kendo-template"> <ul> # for (var i = 0; i < data.length; i++) { # <li>#= data[i] #</li> # } # </ul> </script> <div id="container1"></div> <script type="text/javascript"> $(function () { var data = ['User 1', 'User 2', 'User 3']; var template = kendo.template($("#javascriptTemplate").html()); var result = template(data); //Execute the template $("#container1").html(result); //Append the result }); </script>
در ادامه باید این قالب را رندر کرد. برای این منظور یک div با id مساوی container1 را جهت تعیین محل رندر نهایی اطلاعات مشخص میکنیم. سپس متد kendo.template بر اساس id قالب اسکریپتی تعریف شده، یک شیء قالب را تهیه کرده و سپس با ارسال آرایهای به آن، سبب اجرای آن میشود. خروجی نهایی، یک قطعه کد HTML است که در محل container1 درج خواهد شد.
همانطور که ملاحظه میکنید، متد kendo.template، نهایتا یک رشته را دریافت میکند. بنابراین همینجا و به صورت inline نیز میتوان یک قالب را تعریف کرد.
کار با منابع داده راه دور
فرض کنید مدل برنامه به صورت ذیل تعریف شدهاست:
namespace KendoUI04.Models { public class Product { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsAvailable { set; get; } } }
using System.Collections.Generic; using System.Linq; using System.Web.Http; using KendoUI04.Models; namespace KendoUI04.Controllers { public class ProductsController : ApiController { public IEnumerable<Product> Get() { return ProductDataSource.LatestProducts.Take(10); } } }
<!--دریافت اطلاعات از سرور--> <div> <div id="container2"><ul></ul></div> </div> <script id="template1" type="text/x-kendo-template"> <li> #=Id# - #:Name# - #=kendo.toString(Price, "c")#</li> </script> <script type="text/javascript"> $(function () { var producatsTemplate1 = kendo.template($("#template1").html()); var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, error: function (e) { alert(e.errorThrown); }, change: function () { $("#container2 > ul").html(kendo.render(producatsTemplate1, this.view())); } }); productsDataSource.read(); }); </script>
هرچند خروجی دریافتی از سرور نهایتا یک آرایه از اشیاء Product است، اما در template1 اثری از حلقهی جاوا اسکریپتی مشاهده نمیشود. در اینجا چون از متد kendo.render استفاده میشود، نیازی به ذکر حلقه نیست و به صورت خودکار، به تعداد عناصر آرایه دریافتی از سرور، قطعه HTML قالب را تکرار میکند.
در ادامه برای کار با سرور از یک Kendo UI DataSource استفاده شدهاست. قسمت transport/read آن، کار تعریف محل دریافت اطلاعات را از سرور مشخص میکند. رویدادگران change آن اطلاعات نهایی دریافتی را توسط متد view در اختیار متد kendo.render قرار میدهد. در نهایت، قطعهی HTML رندر شدهی نهایی حاصل از اجرای قالب، در بین تگهای ul مربوط به container2 درج خواهد شد.
رویدادگران change زمانیکه data source، از اطلاعات راه دور و یا یک آرایهی جاوا اسکریپتی پر میشود، فراخوانی خواهد شد. همچنین مباحث مرتب سازی اطلاعات، صفحه بندی و تغییر صفحه، افزودن، ویرایش و یا حذف اطلاعات نیز سبب فراخوانی آن میگردند. متد view ایی که در این مثال فراخوانی شد، صرفا در روال رویدادگردان change دارای اعتبار است و آخرین تغییرات اطلاعات و آیتمهای موجود در data source را باز میگرداند.
یک نکتهی تکمیلی: فعال سازی intellisense کدهای جاوا اسکریپتی Kendo UI
اگر به پوشهی اصلی مجموعهی Kendo UI مراجعه کنید، یکی از آنها vsdoc نام دارد که داخل آن فایلهای min.intellisense.js و vsdoc.js مشهود هستند.
اگر از ویژوال استودیوهای قبل از 2012 استفاده میکنید، نیاز است فایلهای vsdoc.js متناظری را به پروژه اضافه نمائید؛ دقیقا در کنار فایلهای اصلی js موجود. اگر از ویژوال استودیوی 2012 و یا بالاتر استفاده میکنید باید از فایلهای intellisense.js متناظر استفاده کنید. برای مثال اگر از kendo.all.min.js کمک میگیرید، فایل متناظر با آن kendo.all.min.intellisense.js خواهد بود.
بعد از اینکار نیاز است فایلی به نام references.js_ را به پوشهی اسکریپتهای خود با این محتوا اضافه کنید (برای VS 2012 به بعد):
/// <reference path="jquery.min.js" /> /// <reference path="kendo.all.min.js" />
Tools menu –> Options -> Text Editor –> JavaScript –> Intellisense –> References
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
KendoUI04.zip
آموزش TypeScript #1
در تصویر ذیل یک مقایسه کوتاه بین CoffeeScript و TypeScript را مشاهده میکنید.
با TypeScript چه چیزهایی به دست خواهیم آورد؟
یک نکته مهم این است که این زبان به خوبی در Visual Studio پشتیبانی میشود و قابلیت Intellisense نوشتن برنامه به این زبان را دلپذیرتر خواهد کرد و از طرفی دیگر به نظر من یکی از مهمترین مزیت هایی که TypeScript در اختیار ما قرار میدهد این است که میتوانیم به صورت Syntax آشنای شی گرایی کد نویسی کنیم و خیلی راحتتر کدهای خود را سازمان دهی کرده و از نوشتن کدهای تکراری اجتناب کنیم.
یکی دیگر از مزیتهای مهم این زبان این است که این زبان از Static Typing به خوبی پشتیبانی میکند. این بدین معنی است که شما ابتدا باید متغیرها را تعریف کرده و نوع آنها را مشخص نمایید و هم چنین در هنگام پاس دادن مقادیر به پارامترهای توابع باید حتما به نوع داده ای آنها دقت داشته باشید چون کامپایلر بین انواع داده ای در TypeScript تمایز قایل است و در صورت رعایت نکردن این مورد شما با خطا مواجه خواهید شد. این تمایز قایل شدن باعث میشود که برنامه هایی خواناتر داشته باشیم از طرفی باعث میشود که خطا یابی و نوشتن تست برای برنامه راحتتر و تمیزتر باشد. بر خلاف JavaScript، در TypeScript(به دلیل پشتیبانی از شی گرایی) میتوانیم علاوه بر داشتن کلاس، اینترفیس نیز داشته باشیم و در حال حاضر مزایای استفاده از اینترفیس بر کسی پوشیده نیست.
به دلیل اینکه کدهای TypeScript ابتدا کامپایل شده و بعد تبدیل به کدهای JavaScript میشوند در نتیجه قبل از رسیدن به مرحله اجرای پروژه، ما از خطاهای موجود در کد خود مطلع خواهیم شد.
البته این نکته را نیز فراموش نخواهیم کرد که این زبان تازه متولد شده است(سال 2012 توسط Anders Hejlsberg) و همچنان در حال توسعه است و این در حال حاضر مهمترین عیب این زبان میتواند باشد چون هنوز به پختگی سایر زبانهای اسکریپتی در نیامده است.
در ذیل یک مثال کوچک به زبان TypeScript و JavaScript را برای مقایسه در خوانایی و راحتی کد نویسی قرار دادم:
TypeScript:
class Greeter { greeting: string; constructor (message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } }
var Greeter = (function () { function Greeter(message) { this.greeting = message; } Greeter.prototype.greet = function () { return "Hello, " + this.greeting; }; return Greeter; })();
Program : یک برنامه TypeScript مجموعه ای از یک یا چند Source File است. این Source Fileها شامل کدهای پیاده سازی برنامه هستند ولی در خیلی موارد برای خوانایی بیشتر برنامه میتوان فقط تعاریف را در این فایلهای سورس قرار داد.
Module: ماژول در TypeScript شبیه به مفاهیم فضای نام یا namespace در دات نت است و میتواند شامل چندین کلاس یا اینترفیس باشد.
Class : مشابه به مفاهیم کلاس در دات نت است و دقیقا همان مفهوم را دارد. یک کلاس میتواند شامل چندین تابع و متغیر با سطوح دسترسی متفاوت باشد. در TypeScript مجاز به استفاده از کلمات کلیدی public و private نیز میباشید. یک کلاس در Typescript میتواند یک کلاس دیگر را توسعه دهد(ارث بری در دات نت) و چندین اینترفیس را پیاده سازی نماید.
Interface: یک اینترفیس فقط شامل تعاریف است و پیاده سازی در آن انجام نخواهد گرفت. یک اینترفیس میتواند چندین اینترفیس دیگر را توسعه دهد.
Function: معادل متد در دات نت است. میتواند پارامتر ورودی داشته باشد و در صورت نیاز یک مقدار را برگشت دهد.
Scope: دقیقا تمام مفاهیم مربوط به محدوده فضای نام و کلاس و متد در دات نت در این جا نیز صادق است.
آماده سازی Visual Studio برای شروع به کار
در ابتدا باید Template مربوطه به TypeScript را نصب کنید تا از طریف VS.Net بتوانیم به راحتی به این زبان کد نویسی کنیم. میتوانید فایل نصب را از اینجا دانلود کنید. بعد از نصب از قسمت Templateهای موجود گزینه Html Application With TypeScript را انتخاب کنید
یا از قسمت Add در پروژههای وب خود نظیر MVC گزینه TypeScript File را انتخاب نمایید.
در پست بعدی کد نویسی با این زبان را آغاز خواهیم کرد.
تصویر فوق، یکی از تصویرهایی است که شاید از طریق ایمیلهایی تحت عنوان "فقط در ایران!" به دست شما هم رسیده باشد. تصور کاربر نهایی (که این ایمیل را با تعجب ارسال کرده) این است که در اینجا به او گفته شده مثلا "مرتضی" را جستجو نکنید و امثال آن. چون برای او تفاوتی بین ی و ی وجود ندارد. همچنین بکار بردن "اقلامی" هم کمی غلط انداز است و بیشتر ذهن را به سمت کلمه سوق میدهد تا حرف.
در ادامهی بحث آلرژی مزمن به وجود انواع "ی" و "ک" در بانک اطلاعاتی (+ و + و +)، اینبار قصد داریم این اطلاعات را به NHibernate بسط دهیم. شاید یک روش اعمال یک دست سازی "ی" و "ک" این باشد که در کل برنامه هر جایی که قرار است update یا insert ایی صورت گیرد، خواص رشتهای را یافته و تغییر دهیم. این روش "کار میکنه" ولی ایده آل نیست؛ چون حجم کار تکراری در برنامه زیاد خواهد شد و نگهداری آن هم مشکل میشود. همچنین امکان فراموش کردن اعمال آن هم وجود دارد.
در NHibernate یک سری EventListener وجود دارند که کارشان گوش فرا دادن به یک سری رخدادها مانند مثلا update یا insert است. این رخدادها میتوانند پیش یا پس از هرگونه ثبت یا ویرایشی در برنامه صادر شوند. بنابراین بهترین جایی که جهت اعمال این نوع ممیزی (Auditing) بدون بالا بردن حجم برنامه یا اضافه کردن بیش از حد یک سری کد تکراری در حین کار با NHibernate میتوان یافت، روالهای مدیریت کنندهی همین EventListener ها هستند.
کلاس YeKeAuditorEventListener نهایی با پیاده سازی IPreInsertEventListener و IPreUpdateEventListenerبه شکل زیر خواهد بود:
using NHibernate.Event;
namespace NHYeKeAuditor
{
public class YeKeAuditorEventListener : IPreInsertEventListener, IPreUpdateEventListener
{
// Represents a pre-insert event, which occurs just prior to performing the
// insert of an entity into the database.
public bool OnPreInsert(PreInsertEvent preInsertEvent)
{
var entity = preInsertEvent.Entity;
CorrectYeKe.ApplyCorrectYeKe(entity);
return false;
}
// Represents a pre-update event, which occurs just prior to performing the
// update of an entity in the database.
public bool OnPreUpdate(PreUpdateEvent preUpdateEvent)
{
var entity = preUpdateEvent.Entity;
CorrectYeKe.ApplyCorrectYeKe(entity);
return false;
}
}
}
تا اینجا فقط تعریف YeKeAuditorEventListener انجام شده است. اما NHibernate چگونه از وجود آن مطلع خواهد شد؟
برای تزریق کلاس YeKeAuditorEventListener به تنظیمات برنامه باید به شکل زیر عمل کرد:
using System;
using System.Linq;
using FluentNHibernate.Cfg;
using NHibernate.Cfg;
namespace NHYeKeAuditor
{
public static class MappingsConfiguration
{
public static FluentConfiguration InjectYeKeAuditorEventListener(this FluentConfiguration fc)
{
return fc.ExposeConfiguration(configListeners());
}
private static Action<Configuration> configListeners()
{
return
c =>
{
var listener = new YeKeAuditorEventListener();
c.EventListeners.PreInsertEventListeners =
c.EventListeners.PreInsertEventListeners
.Concat(new[] { listener })
.ToArray();
c.EventListeners.PreUpdateEventListeners =
c.EventListeners.PreUpdateEventListeners
.Concat(new[] { listener })
.ToArray();
};
}
}
}
کدهای NHYeKeAuditor را از اینجا میتوانید دریافت کنید.
OpenCVSharp #12
در تصویر ذیل، تصویر یک راهرو را مشاهده میکنید که توسط ماوس قطعه بندی شدهاست (تصویر اصلی یا سمت چپ). تصویر سمت راست، نسخهی قطعه بندی شدهی این تصویر به کمک الگوریتم watershed است.
همانطور که در تصویر نیز مشخص است، نمایش هر ناحیهی قطعه بندی شده، شبیه به سیلان آب است که با رسیدن به مرز قطعهی بعدی متوقف شدهاست. به همین جهت به آن watershed (آب پخشان) میگویند.
انتخاب نواحی مختلف به کمک ماوس
در اینجا کدهای آغازین مثال بحث جاری را ملاحظه میکنید:
var src = new Mat(@"..\..\Images\corridor.jpg", LoadMode.AnyDepth | LoadMode.AnyColor); var srcCopy = new Mat(); src.CopyTo(srcCopy); var markerMask = new Mat(); Cv2.CvtColor(srcCopy, markerMask, ColorConversion.BgrToGray); var imgGray = new Mat(); Cv2.CvtColor(markerMask, imgGray, ColorConversion.GrayToBgr); markerMask = new Mat(markerMask.Size(), markerMask.Type(), s: Scalar.All(0)); var sourceWindow = new Window("Source (Select areas by mouse and then press space)") { Image = srcCopy }; var previousPoint = new Point(-1, -1); sourceWindow.OnMouseCallback += (@event, x, y, flags) => { if (x < 0 || x >= srcCopy.Cols || y < 0 || y >= srcCopy.Rows) { return; } if (@event == MouseEvent.LButtonUp || !flags.HasFlag(MouseEvent.FlagLButton)) { previousPoint = new Point(-1, -1); } else if (@event == MouseEvent.LButtonDown) { previousPoint = new Point(x, y); } else if (@event == MouseEvent.MouseMove && flags.HasFlag(MouseEvent.FlagLButton)) { var pt = new Point(x, y); if (previousPoint.X < 0) { previousPoint = pt; } Cv2.Line(img: markerMask, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5); Cv2.Line(img: srcCopy, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5); previousPoint = pt; sourceWindow.Image = srcCopy; } };
همچنین این نسخهی سیاه و سفید تک کاناله به یک تصویر سه کاناله برای نمایش رنگهای قسمتهای مختلف قطعه بندی شده، تبدیل میشود.
سپس پنجرهی نمایش تصویر اصلی برنامه ایجاد شده و در اینجا روال رخدادگردان OnMouseCallback آن به صورت inline مقدار دهی شدهاست. در این روال میتوان مدیریت ماوس را به عهده گرفت و کار نمایش خطوط مختلف را با فشرده شدن و سپس رها شدن کلیک سمت چپ ماوس انجام داد.
خط ترسیم شده بر روی دو تصویر از نوع Mat نمایش داده میشود. تصویر srcCopy، همان تصویر نمایش داده شدهی در پنجرهی اصلی است و تصویر markerMask، بیشتر جنبهی محاسباتی دارد و در متدهای بعدی OpenCV استفاده خواهد شد.
تشخیص کانتورها (Contours) در تصویر
پس از ترسیم نواحی مورد نظر توسط ماوس، یک سری خطوط به هم پیوسته در شکل قابل مشاهده هستند. میخواهیم این خطوط را تشخیص داده و سپس از آنها جهت محاسبات قطعه بندی تصویر استفاده کنیم. تشخیص این خطوط متصل، توسط متدی به نام FindContours انجام میشود. کانتورها، قسمتهای خارجی اجزای متصل به هم هستند.
Point[][] contours; //vector<vector<Point>> contours; HiearchyIndex[] hierarchyIndexes; //vector<Vec4i> hierarchy; Cv2.FindContours( markerMask, out contours, out hierarchyIndexes, mode: ContourRetrieval.CComp, method: ContourChain.ApproxSimple);
متد FindContours یک تصویر 8 بیتی تک کاناله را دریافت میکند. اگر mode آن CCOMP یا FLOODFILL تعریف شود، امکان دریافت یک تصویر 32 بیتی را نیز خواهد داشت.
پارامتر hierarchy آن یک پارامتر اختیاری است که بیانگر اطلاعات topology تصویر است.
توسط پارامتر Mode، نحوهی استخراج کانتور مشخص میشود. اگر به external تنظیم شود، تنها کانتورهای خارجیترین قسمتها را تشخیص میدهد. اگر مساوی list قرار گیرد، تمام کانتورها را بدون ارتباطی با یکدیگر و بدون تشکیل hierarchy استخراج میکند. حالت ccomp تمام کانتورها را استخراج کرده و یک درخت دو سطحی از آنها را تشکیل میدهد. در سطح بالایی مرزهای خارجی اجزاء وجود دارند و در سطح دوم مرزهای حفرهها مشخص شدهاند. حالت و مقدار tree به معنای تشکیل یک درخت کامل از کانتورهای یافت شدهاست.
پارامتر method اگر به none تنظیم شود، تمام نقاط کانتور ذخیره خواهند شد و اگر به simple تنظیم شود، قطعههای افقی، عمودی و قطری، فشرده شده و تنها نقاط نهایی آنها ذخیره میشوند. برای مثال در این حالت یک کانتور مستطیلی، تنها با 4 نقطه ذخیره میشود.
ترسیم کانتورهای تشخیص داده شده بر روی تصویر
میتوان به کمک متد DrawContours، مرزهای کانتورهای یافت شده را ترسیم کرد:
var markers = new Mat(markerMask.Size(), MatType.CV_32S, s: Scalar.All(0)); var componentCount = 0; var contourIndex = 0; while ((contourIndex >= 0)) { Cv2.DrawContours( markers, contours, contourIndex, color: Scalar.All(componentCount + 1), thickness: -1, lineType: LineType.Link8, hierarchy: hierarchyIndexes, maxLevel: int.MaxValue); componentCount++; contourIndex = hierarchyIndexes[contourIndex].Next; }
اعمال الگوریتم watershed
در مرحلهی آخر، تصویر کانتورهای ترسیم شده را به متد Watershed ارسال میکنیم. پارامتر اول آن تصویر اصلی است و پارامتر دوم، یک پارامتر ورودی و خروجی محسوب میشود و کار قطعه بندی تصویر بر روی آن انجام خواهد شد.
کار الگوریتم watershed، ایزوله سازی اشیاء موجود در تصویر از پس زمینهی آنها است. این الگوریتم، یک تصویر سیاه و سفید را دریافت میکند؛ به همراه یک تصویر ویژه به نام marker. تصویر marker کارش مشخص سازی اشیاء، از پس زمینهی آنها است که در اینجا توسط ماوس ترسیم و سپس به کمک یافتن کانتورها و ترسیم آنها بهینه سازی شدهاست.
var rnd = new Random(); var colorTable = new List<Vec3b>(); for (var i = 0; i < componentCount; i++) { var b = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255); var g = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255); var r = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255); colorTable.Add(new Vec3b((byte)b, (byte)g, (byte)r)); } Cv2.Watershed(src, markers); var watershedImage = new Mat(markers.Size(), MatType.CV_8UC3); // paint the watershed image for (var i = 0; i < markers.Rows; i++) { for (var j = 0; j < markers.Cols; j++) { var idx = markers.At<int>(i, j); if (idx == -1) { watershedImage.Set(i, j, new Vec3b(255, 255, 255)); } else if (idx <= 0 || idx > componentCount) { watershedImage.Set(i, j, new Vec3b(0, 0, 0)); } else { watershedImage.Set(i, j, colorTable[idx - 1]); } } } watershedImage = watershedImage * 0.5 + imgGray * 0.5; Cv2.ImShow("Watershed Transform", watershedImage); Cv2.WaitKey(1); //do events
پس از اعمال متد Watershed، هر نقطهی تصویر marker مشخص میکند که متعلق به کدام قطعهی تشخیص داده شدهاست. سپس به این نقطه، رنگ آن قطعه را نسبت داده و آنرا در تصویر جدیدی ترسیم میکنیم.
در آخر، پس زمینه، با نواحی تشخیص داده ترکیب شدهاند (watershedImage * 0.5 + imgGray * 0.5) تا تصویر ابتدای بحث حاصل شود. اگر این ترکیب صورت نگیرد، چنین تصویری حاصل خواهد شد:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
مدلهای AuditLog (اصلاحیه)و ActivityLog
- استفاده از جداول جدا برای هر کدام از جداول به صورتیکه یک ارتباط یک به چند مابین آنها برقرار است. از این جداول تحت عنوان HistoryTable یاد میشود.
- استفاده از یک جدول برای نگهداری تاریخچهی تغییرات جداولی که نیازمند این امکان هستند.
/// <summary> /// Represent The Operation's log /// </summary> public class AuditLog { #region Ctor /// <summary> /// Create One Instance Of <see cref="AuditLog"/> /// </summary> public AuditLog() { Id = SequentialGuidGenerator.NewSequentialGuid(); OperatedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// sets or gets identifier of AuditLog /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets Type of Modification(create,softDelet,Delete,update) /// </summary> public virtual AuditAction Action { get; set; } /// <summary> /// sets or gets description of Log /// </summary> public virtual string Description { get; set; } /// <summary> /// sets or gets when log is operated /// </summary> public virtual DateTime OperatedOn { get; set; } /// <summary> /// sets or gets Type Of Entity /// </summary> public virtual string Entity { get; set; } /// <summary> /// gets or sets Old value of Properties before modification /// </summary> public virtual string XmlOldValue { get; set; } /// <summary> /// gets or sets XML Base OldValue of Properties (NotMapped) /// </summary> public virtual XElement XmlOldValueWrapper { get { return XElement.Parse(XmlOldValue); } set { XmlOldValue = value.ToString(); } } /// <summary> /// gets or sets new value of Properties after modification /// </summary> public virtual string XmlNewValue { get; set; } /// <summary> /// gets or sets XML Base NewValue of Properties (NotMapped) /// </summary> public virtual XElement XmlNewValueWrapper { get { return XElement.Parse(XmlNewValue); } set { XmlNewValue = value.ToString(); } } /// <summary> /// gets or sets Identifier Of Entity /// </summary> public virtual string EntityId { get; set; } /// <summary> /// gets or sets user agent information /// </summary> public virtual string Agent { get; set; } /// <summary> /// gets or sets user's ip address /// </summary> public virtual string OperantIp { get; set; } #endregion #region NavigationProperties /// <summary> /// sets or gets log's creator /// </summary> public virtual User Operant { get; set; } /// <summary> /// sets or gets identifier of log's creator /// </summary> public virtual long OperantId { get; set; } #endregion } public enum AuditAction { Create, Update, Delete, SoftDelete, }
- Action : از نوع AdutiAction است و برای مشخص کردن نوع عملیاتی که انجام شده است، میباشد.
- Description : اگر نیاز باشد توضیحاتی اضافی ثبت شوند، از این خصوصیت استفاده میشود.
- Entity : مشخص کنندهی نام مدل خواهد بود. شاید بهتر بود از یک Enum استفاده میشد. ولی این سیستم به احتمال زیاد قرار است افزونه پذیر باشد و استفاده از Enum، یعنی محدودیت و این امکان وجود نخواهد داشت که سایر افزونهها بتوانند از مدل بالا استفاده کنند. برا ی مثال BlogPost , NewsItem , ForumPost , ...
- EntitytId : آی دی رکوردی است که تاریخچهی آن ثبت شده است. از آنجائیکه بعضی از موجودیتها دارای آی دی از نوع long و برخی دیگر Guid ، لذا ذخیرهی رشتهای آن مفید خواهد بود.
- XmlOldValue : در برگیرندهی مقدار (قبل از اعمال تغییرات) خصوصیاتی است که لازم است از یک موجودیت مشخص، در قالب XML رشتهای ذخیره شوند.
- XmlNewValue : در برگیرندهی مقدار (بعد از تغییرات) خصوصیاتی است که لازم است از یک موجودیت مشخص، در قالب XML رشتهای ذخیره شوند.
- Operant, OperantId: برای برقراری ارتباط یک به چند مابین مدل کاربر و مدل بالا در نظر گرفته شدهاند که به عنوان انجام دهندهی این تغییرات بوده است.
/// <summary> /// Represents Activity Log record /// </summary> public class ActivityLog { #region Ctor /// <summary> /// Create one instance of <see cref="ActivityLog"/> /// </summary> public ActivityLog() { Id = SequentialGuidGenerator.NewSequentialGuid(); OperatedOn=DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets the comment of this activity /// </summary> public virtual string Comment { get; set; } /// <summary> /// gets or sets the date that this activity was done /// </summary> public virtual DateTime OperatedOn { get; set; } /// <summary> /// gets or sets the page url . /// </summary> public virtual string Url { get; set; } /// <summary> /// gets or sets the title of page if Url is Not null /// </summary> public virtual string Title { get; set; } /// <summary> /// gets or sets user agent information /// </summary> public virtual string Agent { get; set; } /// <summary> /// gets or sets user's ip address /// </summary> public virtual string OperantIp { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets the type of this activity /// </summary> public virtual ActivityLogType Type{ get; set; } /// <summary> /// gets or sets the type's id of this activity /// </summary> public virtual Guid TypeId { get; set; } /// <summary> /// gets or sets User that done this activity /// </summary> public virtual User Operant { get; set; } /// <summary> /// gets or sets Id of User that done this activity /// </summary> public virtual long OperantId { get; set; } #endregion } /// <summary> /// Represents Activity Log Type Record /// </summary> public class ActivityLogType { #region Ctor /// <summary> /// Create one Instance of <see cref="ActivityLogType"/> /// </summary> public ActivityLogType() { Id = SequentialGuidGenerator.NewSequentialGuid(); } #endregion #region Properties /// <summary> /// gets or sets identifier /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets the system name /// </summary> public virtual string Name{ get; set; } /// <summary> /// gets or sets the display name /// </summary> public virtual string DisplayName { get; set; } /// <summary> /// gets or sets the description /// </summary> public virtual string Description { get; set; } /// <summary> /// indicate this log type is enable for logging /// </summary> public virtual bool IsEnabled { get; set; } #endregion }
- Comment : توضیحات کوتاهی از اکشنی که کاربر انجام داده است.
- Url : آدرس صفحهای که این عملیات در آنجا انجام شده است. این خصوصیت نالپذیر میباشد.
- Title : عنوان صفحهای که این عملیات در آنجا انجام شده است؛ اگر Url نال نباشد.
- Operant , OperantId : برای برقراری ارتباط یک به چند بین کاربر و مدل فعالیتها در نظر گرفته شدهاند.
- Type : از نوع ActivityLogType پیاده سازی شده در بالا میباشد. با استفاده از مدل ActivityLogType میتوان مثلا لاگ فعالیت مربوط به بخش اخبار را غیر فعال کند یا بالعکس و از این موارد.
- Name : نام سیستمی آن است. برای مثال : Login ، NewsComment ، NewsItem و ...
- IsEnabled : نشان دهندهی این است که این نوع لاگ فعال است یا خیر.
کلاس پایه تمام مدلها (اصلاحیه)
/// <summary> /// Represents the entity /// </summary> /// <typeparam name="TForeignKey">type of user's Id that can be long or long? </typeparam> public abstract class Entity<TForeignKey> { #region Properties /// <summary> /// gets or sets date that this entity was created /// </summary> public virtual DateTime CreatedOn { get; set; } /// <summary> /// gets or sets Date that this entity was updated /// </summary> public virtual DateTime ModifiedOn { get; set; } /// <summary> /// gets or sets IP Address of Creator /// </summary> public virtual string CreatorIp { get; set; } /// <summary> /// gets or set IP Address of Modifier /// </summary> public virtual string ModifierIp { get; set; } /// <summary> /// indicate this entity is Locked for Modify /// </summary> public virtual bool ModifyLocked { get; set; } /// <summary> /// indicate this entity is deleted softly /// </summary> public virtual bool IsDeleted { get; set; } /// <summary> /// gets or sets information of user agent of modifier /// </summary> public virtual string ModifierAgent { get; set; } /// <summary> /// gets or sets information of user agent of Creator /// </summary> public virtual string CreatorAgent { get; set; } /// <summary> /// gets or sets date that this entity repoted last time /// </summary> public virtual DateTime? ReportedOn { get; set; } /// <summary> /// gets or sets counter for Content's report /// </summary> public virtual int ReportsCount { get; set; } /// <summary> /// gets or sets count of Modification Default is 1 /// </summary> public virtual int Version { get; set; } /// <summary> /// gets or sets action (create,update,softDelete) /// </summary> public virtual AuditAction Action { get; set; } /// <summary> /// gets or sets TimeStamp for prevent concurrency Problems /// </summary> public virtual byte[] RowVersion { get; set; } #endregion #region NavigationProperties /// <summary> /// gets ro sets User that Modify this entity /// </summary> public virtual User ModifiedBy { get; set; } /// <summary> /// gets ro sets Id of User that modify this entity /// </summary> public virtual TForeignKey ModifiedById { get; set; } /// <summary> /// gets ro sets User that Create this entity /// </summary> public virtual User CreatedBy { get; set; } /// <summary> /// gets ro sets User that Create this entity /// </summary> public virtual TForeignKey CreatedById { get; set; } #endregion } /// <summary> /// Represents the base Entity /// </summary> /// <typeparam name="TKey">type of Id</typeparam> /// <typeparam name="TForeignKey">type of User's Id that can be long or long?</typeparam> public abstract class BaseEntity<TKey,TForeignKey> : Entity<TForeignKey> { #region Properties /// <summary> /// gets or sets Identifier of this Entity /// </summary> public virtual TKey Id { get; set; } #endregion }
مدل سیستم آگاه سازی
/// <summary> /// Represents the Notification Record /// </summary> public class Notification { #region Ctor /// <summary> /// create one instance of <see cref="Notification"/> /// </summary> public Notification() { Id = SequentialGuidGenerator.NewSequentialGuid(); ReceivedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier /// </summary> public virtual Guid Id { get; set; } /// <summary> /// indicate that this notification is read by owner /// </summary> public virtual bool IsRead { get; set; } /// <summary> /// gets or sets notification's text body /// </summary> public virtual string Message { get; set; } /// <summary> /// gets or sets page url that this notification is related with it /// </summary> public virtual string Url { get; set; } /// <summary> /// gets or sets date that this Notification Received /// </summary> public virtual DateTime ReceivedOn { get; set; } /// <summary> /// gets or sets the type of notification /// </summary> public virtual NotificationType Type { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets the id of user that is owner of this notification /// </summary> public virtual long OwnerId { get; set; } /// <summary> /// gets or sets the user that is owner of this notification /// </summary> public virtual User Owner { get; set; } #endregion } public enum NotificationType { NewConversation, NewConversationReply, ... }
- IsRead : مشخص کنندهی این است که یک اطلاع رسانی خوانده شده است یا خیر. در آخر هر روز اطلاع رسانیهایی که دارای خصوصیت IsRead با مقدار true هستند، حذف خواهند شد.
- Url : برای مواردی که لازم است کاربر با کلیک بر روی آن به صفحهی خاصی هدایت شود.
- OwnerId, Owner : برای برقراری ارتباط یک به چند بین کاربر و مدل Notification در نظر گرفته شدهاند.
- Type : از نوع NotificationType و مشخص کنندهی نوع اطلاع رسانی میباشد .
مدل Observation
public class Observation { #region Ctor /// <summary> /// create one instance of <see cref="Observation"/> /// </summary> public Observation() { LastObservedOn = DateTime.Now; Id = SequentialGuidGenerator.NewSequentialGuid(); } #endregion #region Properties public virtual Guid Id { get; set; } /// <summary> /// gets or sets datetime of last visit /// </summary> public virtual DateTime LastObservedOn { get; set; } /// <summary> /// gets or sets Id Of section That user is observing the entity /// </summary> public virtual string SectionId { get; set; } /// <summary> /// gets or sets section That user is observing in it /// </summary> public virtual string Section { get; set; } #endregion #region NavigationProperites /// <summary> /// gets or sets user that observed the entity /// </summary> public virtual User Observer { get; set; } /// <summary> /// gets or sets identifier of user that observed the entity /// </summary> public virtual long ObserverId { get; set; } #endregion }
مدل صفحات داینامیک
/// <summary> /// represents one custom page /// </summary> public class Page : BaseEntity<long, long> { #region Properties /// <summary> /// gets or sets the blog pot body /// </summary> public virtual string Body { get; set; } /// <summary> /// gets or sets the content title /// </summary> public virtual string Title { get; set; } /// <summary> /// gets or sets value indicating Custom Slug /// </summary> public virtual string SlugUrl { get; set; } /// <summary> /// gets or sets meta title for seo /// </summary> public virtual string MetaTitle { get; set; } /// <summary> /// gets or sets meta keywords for seo /// </summary> public virtual string MetaKeywords { get; set; } /// <summary> /// gets or sets meta description of the content /// </summary> public virtual string MetaDescription { get; set; } /// <summary> /// gets or sets /// </summary> public virtual string FocusKeyword { get; set; } /// <summary> /// gets or sets value indicating whether the content use CanonicalUrl /// </summary> public virtual bool UseCanonicalUrl { get; set; } /// <summary> /// gets or sets CanonicalUrl That the Post Point to it /// </summary> public virtual string CanonicalUrl { get; set; } /// <summary> /// gets or sets value indicating whether the content user no Follow for Seo /// </summary> public virtual bool UseNoFollow { get; set; } /// <summary> /// gets or sets value indicating whether the content user no Index for Seo /// </summary> public virtual bool UseNoIndex { get; set; } /// <summary> /// gets or sets value indicating whether the content in sitemap /// </summary> public virtual bool IsInSitemap { get; set; } /// <summary> /// gets or sets title for snippet /// </summary> public string SocialSnippetTitle { get; set; } /// <summary> /// gets or sets description for snippet /// </summary> public string SocialSnippetDescription { get; set; } /// <summary> /// gets or sets section's type that this page show on /// </summary> public virtual ShowPageSection Section { get; set; } /// <summary> /// indicate this page has not any body /// </summary> public virtual bool IsCategory { get; set; } /// <summary> /// gets or sets order for display forum /// </summary> public virtual int DisplayOrder { get; set; } #endregion #region NavigationProeprties /// <summary> /// gets or sets Parent of this page /// </summary> public virtual Page Parent { get; set; } /// <summary> /// gets or sets parent'id of this page /// </summary> public virtual long? ParentId { get; set; } /// <summary> /// get or set collection of page that they are children of this page /// </summary> public virtual ICollection<Page> Children { get; set; } #endregion } public enum ShowPageSection { Menu, Footer, SideBar }
نتیجهی تا این قسمت
سازماندهی برنامههای Angular توسط ماژولها
- ویژگی SkipSelf به معنای الزام به استفادهی از وهلهی تزریق شدهی توسط والد هست؛ بجای وهلهی ارائه شدهی توسط تزریق کنندهی فعلی. برای مثال استفادهی از ماژولهای lazy loaded سبب ایجاد یک تزریق کنندهی خاص خود آنها میشود که با Root Injector یکی نیست. به همین جهت در اینجا اعلام میکنیم که از تزریق کنندهی جاری صرفنظر کن و با Root Injector کار کن.
بستهی Microsoft.AspNetCore.All فقط مخصوص پروژههای netcoreapp2.0 است
این «متا پکیج» تنها در پروژههایی که TargetFramework آنها به netcoreapp2.0 تنظیم شدهاست، قابل استفاده میباشد:
<TargetFramework>netcoreapp2.0</TargetFramework>
نحوهی به روز رسانی پروژهها جهت استفادهی از Microsoft.AspNetCore.All
پیش از ناقص کردن برنامه و حذف بستههای نیوگتی که نباید از فایل csproj حذف شوند، ابتدا باید لیستی را که توسط «متا پکیج» Microsoft.AspNetCore.All ارائه میشود، بررسی کرد. این لیست را پس از نصب SDK جدید، در آدرس ذیل میتوانید مشاهده کنید:
C:\Program Files\dotnet\store\x64\netcoreapp2.0
روش دیگر یافتن این لیست، مراجعهی به سایت نیوگت و بررسی قسمت dependencies آدرس https://www.nuget.org/packages/Microsoft.AspNetCore.All است:
سپس به فایل csproj ایی که دارای TargetFramework مساوی netcoreapp2.0 است مراجعه کرده و هر کدام از بستههایی را که در این لیست قرار دارند ... حذف کنید. در آخر بجای تمام این مداخل حذف شده، یک مدخل کلی ذیل را تعریف کنید:
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> </ItemGroup>
سؤال: آیا استفادهی از بستهی Microsoft.AspNetCore.All، ارائهی نهایی برنامه را حجیم نمیکند؟
اگر به مسیر dotnet\store ایی که پیشتر عنوان شد مراجعه کنید، در اینجا بیش از 180 بسته را خواهید یافت. در این حالت شاید به نظر برسد که حجم نهایی قابل توزیع برنامههای ASP.NET Core با استفاده از تک مدخل Microsoft.AspNetCore.All بسیار بالا خواهد رفت. اما ... خیر.
NET Core 2.0. به همراه ویژگی جدیدی است به نام Runtime store. هدف از آن، پیش نصب بستهها بر روی سیستم جاری، در یک مکان مرکزی است تا دیگر در حین توزیع نهایی برنامه، نیازی به توزیع مجدد آنها نباشد. به همین جهت، به آن میتوان شبیه به مفهوم پیشین Global assembly cache یا GAC مخصوص NET Core. نگاه کرد. به علاوه تمام این بستهها ngen شده و سرعت آغاز و اجرای برنامهها را بهبود میبخشند.
زمانیکه SDK جدید NET Core 2.0. را نصب میکنید، تمام بستههای مورد نیاز آن، در مسیر مرکزی C:\Program Files\dotnet\store نصب میشوند. بنابراین سیستمی که به همراه این SDK باشد، حتما حاوی تمام وابستگیهای ذکر شدهی در متاپکیج Microsoft.AspNetCore.All نیز خواهد بود و در این حالت نیازی به توزیع مجدد آنها نیست.
پس از آن مهمترین تفاوتی را که مشاهده خواهید کرد، کاهش حجم نهایی برنامههای ASP.NET Core 2.0 نسبت به نگارشهای 1x است. برای آزمایش، یک برنامهی ASP.NET Core 1.x و سپس یک برنامهی سادهی ASP.NET Core 2.x را publish کنید.
این تصویر، پوشهی نهایی قابل توزیع یک برنامهی ASP.NET Core 1.x را پس از publish نمایش میدهد:
و این تصویر، پوشهی نهایی قابل توزیع یک برنامهی ASP.NET Core 2.x را پس از publish نمایش میدهد:
در این حالت پوشهی نهایی نگارش 1x شامل 94 آیتم و پوشهی نهایی نگارش 2x شامل 13 آیتم است. یعنی حجم نهایی را که باید ارائه داد، به شدت کاهش یافتهاست.
بالا رفتن کارآیی تازه واردان به دنیای ASP.NET Core با متاپکیج جدید Microsoft.AspNetCore.All
یکی از مشکلاتی که به همراه کار با ASP.NET Core 1.x وجود دارد، مشخص نبودن محل قرارگیری ویژگیهای جدید و بستههای مرتبط با آنها است. همچنین به ازای هر ویژگی جدید باید یک بستهی نیوگت جدید را نصب کرد و عموما یافتن اینها و یا دانستن وجود آنها، کار دشواری میباشد.
اما زمانیکه متابستهی Microsoft.AspNetCore.All به قسمت ارجاعات پروژه اضافه میشود، در آغاز کار برنامه، سیستم IntelliSense آنها را پردازش کرده و بلافاصله در اختیار برنامه نویس قرار میگیرند. این قابلیت حتی در VSCode نیز همانند Visual Studio کار میکند و توسعه دهندهها بلافاصله IntelliSense بسیار کاملی را از قابلیتهای موجود در اختیار خواهند داشت؛ به همراه ویژگیهای تکمیلی دیگری مانند افزودن و یا اصلاح سادهتر فضاهای نام مرتبط.