مدلهای 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 }
نتیجهی تا این قسمت
با استفاده از پارامترهای آبشاری میتوان شیءای را در اختیار تمام کامپوننتهای قرار گرفته شدهی در سلسله مراتب آنها قرار داد. برای مثال اگر در فایل Client\Shared\MainLayout.razor، جائیکه سایر کامپوننتها قرار است رندر شوند را توسط یک کامپوننت سطح بالا محصور کنیم:
<Alert> @Body </Alert>
بنابراین طراحی سادهی کامپوننت Alert ای (Client\Shared\Alert.razor) که تامین کنندهی یک پارامتر آبشاری سراسری است، به صورت زیر میتواند باشد:
<CascadingValue Value=this> @if(IsVisible) { <div class="alert @Css" role="alert"> @Message <button type="button" class="close" data-dismiss="alert" aria-label="Close" @onclick="HideAlert"> <span aria-hidden="true">×</span> </button> </div> } @ChildContent </CascadingValue> @code { [Parameter] public RenderFragment ChildContent { get; set; } private bool IsVisible; private string Message; private string Css = "alert-primary"; public void ShowAlert(string message, AlertType alertType) { IsVisible = true; Message = message; Css = alertType switch { AlertType.Success => "alert-success", AlertType.Info => "alert-primary", AlertType.Danger => "alert-danger", AlertType.Warning => "alert-warning", _ => "alert-primary" }; StateHasChanged(); } public void HideAlert() { IsVisible = false; } }
namespace BlazorWasmAlert.Client.Shared { public enum AlertType { Success, Info, Danger, Warning } }
الف) وجود یک CascadingValue که اینبار Value آن به خود کامپوننت اشاره میکند (Value=this). یعنی پارامتر آبشاری که در اختیار سایر کامپوننتهای محصور شدهی توسط آن ارسال میشود، دقیقا وهلهای از کامپوننت Alert است که توسط آن میتوان برای مثال، متد عمومی ShowAlert آنرا فراخوانی کرد:
<CascadingValue Value=this>
پس از درج این کامپوننت در فایل layout، روش استفادهی از آن برای مثال در کامپوننت Index به صورت زیر است:
@page "/" <h1>Hello, world!</h1> <button class="btn btn-primary" @onclick="ShowAlert">Show Alert!</button> @code { [CascadingParameter] public Alert Alert { get; set; } private void ShowAlert() { Alert.ShowAlert("This is a test!", AlertType.Info); } }
پ.ن.
در طراحی Blazor، از طراحی React الهام گرفته شدهاست و CascadingValue آن دقیقا معادل Context API جدید React است.
تولید محتوای پویا در Angular
محتوای پویا Angular :
Multiple ways to create Angular components dynamically at runtime
In this article, I am going to show you several ways of creating dynamic content in Angular. You will get examples of custom list templates, dynamic component creation, runtime component and module compilation. Full source code will be available at the end of the article.
Component architectures are an important part of ever modern front-end framework. In this article, I’m going to dissect Polymer, React, Rio.js, Vue.js, Aurelia and Angular 2 components. The goal is to make the commonalities between each solution obvious. Hopefully, this will convince you that learning one or the other isn’t all that complex, given that everyone has somewhat settled on a component architecture.
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
builder.Services.AddServerSideBlazor(options => { options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter"); });
<button onclick="callCounter()">Call Counter</button> <script> async function callCounter() { let containerElement = document.getElementById('my-counter'); await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 }); } </script> <div id="my-counter"> </div>
سازماندهی برنامههای Angular توسط ماژولها
الف) چگونه از import ثانویهی Core Module در سایر ماژولها جلوگیری کنیم؟
Core Module فقط باید در AppModule برنامه import شود و نه در هیچجای دیگری. برای جلوگیری اتفاقی از این مساله میتوان سازندهای را به شکل زیر به آن اضافه کرد:
@NgModule({ imports: [CommonModule, RouterModule], exports: [], // components that are used in app.component.ts will be listed here. declarations: [], // components that are used in app.component.ts will be listed here. providers: [BrowserStorageService, AppConfigService] // global singleton services of the whole app will be listed here. }) export class CoreModule { constructor( @Optional() @SkipSelf() core: CoreModule) { if (core) { throw new Error("CoreModule should be imported ONLY in AppModule."); } } };
از همین روش برای تشخیص singleton بودن یک سرویس نیز میتوان استفاده کرد. خودش را به خودش تزریق میکنیم! اگر تزریقی صورت گرفت، یک خطا را صادر میکنیم.
ب) چگونه از وهله سازی مجدد سرویسهای تعریف شدهی در Shared Module در سایر ماژولها جلوگیری کنیم؟
هدف از قسمت providers در Shared Module تنها ارائهی سرویسهایی جهت کامپوننتهای اشتراکی آن است؛ وگرنه سرویسهای سراسری برنامه در CoreModule تعریف میشوند و این ماژول ویژه نیز تنها یکبار و آنهم در AppModule برنامه import خواهد شد. اما در مورد Shared Module اینطور نیست و اگر این ماژول در یک lazy loaded module استفاده شود، سرویسهای آن طول عمر متفاوتی را پیدا خواهند کرد (هر lazy loaded module یک injector و یک طول عمر خاص خودش را تعریف میکند).
در این حالت برای اینکه سرویسهای Shared Module فقط در AppModule وهله سازی شوند و نه در هیچجای دیگری، روش کار به صورت ذیل است:
- ابتدا آرایهی providers را از تعاریف NgModule آن حذف میکنیم.
- سپس متد ویژهای را به نام forRoot، به کلاس آن اضافه خواهیم کرد:
@NgModule({ imports: [CommonModule], declarations: [], // common and shared components/directives/pipes between more than one module and components will be listed here. exports: [CommonModule], // common and shared components/directives/pipes between more than one module and components will be listed here. /* No providers here! Since they’ll be already provided in AppModule. */ }) export class SharedModule { static forRoot(): ModuleWithProviders { // Forcing the whole app to use the returned providers from the AppModule only. return { ngModule: SharedModule, providers: [ /* All of your services here. It will hold the services needed by `itself`. */] }; } };
سایر ماژولها چون دسترسی به آرایهی حذف شدهی providers این ماژول را ندارند، دیگر نمیتوانند سرویسهای آنرا وهله سازی کنند. اما AppModule با فراخوانی ()SharedModule.forRoot در لیست import خود، تنها یکبار سبب وهله سازی سرویسهای آن میگردد.
بنابراین در اینجا AppModule باید ()SharedModule.forRoot را import کند. سایر ماژولها فقط SharedModule را import میکنند (بدون ذکر متد forRoot). به این ترتیب سرویسهای آن تنها یکبار توسط AppModule در طول عمر برنامه به اشتراک گذاشته میشوند و در این حالت تفاوتی نمیکند که SharedModule در یک lazy loaded module استفاده شدهاست یا خیر.
روش تعریف متد forRoot توسط سیستم مسیریابی Angular نیز استفاده میشود و یک الگوی پذیرفته شده در بین توسعه دهندگان Angular است. برای مثال ()RouterModule.forRoot در AppModule تعریف میشود و ()RouterModule.forChild برای سایر ماژولها.
نمونهای از AppModule ، ShardModule و CoreModule
{ "userIsAuthorizedForCourseTranscripts": false, "modules": [ { "title": "Course Overview", "clips": [ { "title": "Course Overview", "playerParameters": "author=scott-allen&name=aspdotnet-core-1-0-fundamentals-m0&mode=live&clip=0&course=aspdotnet-core-1-0-fundamentals", "transcripts": [ ] } ] }, { "title": "Building Your First ASP.NET Core Application", "clips": [ { "title": "Introduction", "playerParameters": "author=scott-allen&name=aspdotnet-core-1-0-fundamentals-m1&mode=live&clip=0&course=aspdotnet-core-1-0-fundamentals", "transcripts": [ { "displayTime": 0.0, "text": "Hi! This is Scott, and this course will help you build your first application with ASP.NET Core." }, { "displayTime": 7.0, "text": "In this course, we'll be using Visual Studio and the new ASP.NET Framework to build a web application that" } ] } ] } ] }
public class PluralsightCourse { public bool UserIsAuthorizedForCourseTranscripts { get; set; } public PluralsightCourseItems[] Modules { get; set; } } public class PluralsightCourseItems { public string Title { get; set; } public PluralsightCourseClip[] Clips { get; set; } } public class PluralsightCourseClip { public string Title { get; set; } public string PlayerParameters { get; set; } public PluralsightCourseClipTranscript[] Transcripts { get; set; } } public class PluralsightCourseClipTranscript { public float DisplayTime { get; set; } public string Text { get; set; } }
بارگذاری آن توسط کتابخانهی JSON.NET به صورت ذیل خواهد بود:
public static PluralsightCourse ProcessJsonFile(string jsonData) { return JsonConvert.DeserializeObject<PluralsightCourse>(jsonData); }
کدهای کامل این برنامه را از اینجا میتوانید دریافت کنید:
PluralsightJsonTranscripts.V1.0.7z