نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 13 - معرفی View Components
من از این مورد استفاده کردم ولی جوابگو نبود. توی لاگ دنبال ویو با آدرس شبیه آدرس زیر میگشت:
Views/Shared/Components/[NameOfComponent]/SubFolder1/SubFolder2/[NameOfComponent]/Default.cshtml 
یعنی هر آدرسی هم استفاده کردم Views/Shared/Components/[NameOfComponent] رو اولش اضافه کرد
مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت ششم
در این قسمت مدل‌های باقی مانده‌ی از بخش‌هایی را که در مقاله اول مطرح شدند، به اتمام می‌رسانیم. همچنین با بازخوردهایی که در مقالات قبل گرفتیم، در این قسمت تغییرات ایجاد شده‌ی در مدل‌های قسمت‌های قبل را نیز مطرح خواهیم کرد.

مدل‌های AuditLog (اصلاحیه)و ActivityLog

باید توجه داشت که اگر سیستم AuditLog، جزئیات بیشتری را در بر بگیرد، می‌توان از آن به عنوان History هم یاد کرد. در قسمت چهارم برای پست‌های انجمن یک جدول جدا هم به منظور ذخیره سازی تاریخچه‌ی تغییرات، در نظر گرفتیم. فرض کنید که یک سری از جداول دیگر هم نیازمند این امکان باشند! راه حل چیست؟
  1. استفاده از جداول جدا برای هر کدام از جداول به صورتیکه یک ارتباط یک به چند مابین آنها برقرار است. از این جداول تحت عنوان HistoryTable یاد می‌شود.
  2. استفاده از یک جدول برای نگهداری تاریخچه‌ی تغییرات جداولی که نیازمند این امکان هستند. 
در زیر پیاده سازی از روش دوم رو مشاهده میکنید.
  /// <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: برای برقراری ارتباط یک به چند مابین مدل کاربر و مدل بالا در نظر گرفته شده‌اند که به عنوان انجام دهنده‌ی این تغییرات بوده است.
با استفاده از مدل بالا می‌توان متوجه شد که کاربر x چه خصوصیاتی از  موجودیت y را تغییر داده است و این خصوصیات قبل از تغییر چه مقدارهایی داشته‌اند.
  /// <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 می‌توان مثلا لاگ فعالیت مربوط به بخش اخبار را غیر فعال کند یا بالعکس و از این موارد.
خصوصیات مدل 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
    }
از دو کلاس معرفی شده‌ی در بالا برای کپسوله کردن یکسری خصوصیات تکراری استفاده شده است. البته با بهبودهایی نسبت به مقاله‌ی قبل که با مشاهده‌ی خصوصیات آنها قابل فهم خواهد بود. در برخی از مدل‌ها، برای مثال نظرات وبلاگ امکان ارسال نظر برای افراد Anonymous هم وجود داشت؛ لذا CreatedById امکان نال بودن را هم داشت. به همین دلیل برای کاهش کدها، کلاس‌های بالا را به صورت جنریک تعریف کردیم. فایل EDMX نهایی که در انتهای مقاله ضمیمه شده، برای درک تغییرات اعمال شده مفید خواهد.

مدل سیستم آگاه سازی

/// <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,
        ...
    }
در این سیستم برای اطلاع رسانی کاربر، علاوه بر ارسال ایمیل، بحث اطلاع رسانی RealTime را هم خواهیم داشت. اطلاع رسانی‌هایی که توسط کاربر خوانده نشده باشند، در جدول حاصل از مدل Notification ذخیره خواهند شد. خصوصیاتی که نیاز به توضیح دارند:
  • 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
    }
این مدل برای ایجاد امکانی به منظور واکشی لیست افردای که در حال مشاهده‌ی یک بخش خاص هستند، مفید است. فرض کنید در یک انجمن قصد دارید لیست افردای را که در حال مشاهده‌ی آن هستند، در پایین صفحه نمایش دهید. برای تاپیک‌ها هم همین امکان لازم است. لذا مدل بالا مختص مدل خاصی نیست و برای هر بخشی می‌توان از آن استفاده کرد. 
فرض کنیم کاربری قصد هدایت به یک تاپیک را دارد. لذا هنگام هدایت شدن لازم است رکوردی در جدول حاصل از مدل بالا ثبت شود که کاربر x در بخش Topic، در تاریخ d، تاپیک به شماره‌ی y را مشاهده کرد. در صفحه‌ی مشاهده‌ی تاپیک می‌توان لیست افرادی را که قبل از مدت زمان مشخصی تاپیک را مشاهده کرده اند، نمایش داد. این رکورد‌ها را هم با تعریف یک Task می‌توان در بازه‌های زمانی مشخصی حذف کرد.

مدل صفحات داینامیک

/// <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
    }
مدل بالا مشخص کننده‌ی صفحاتی است که مدیر می‌تواند در پنل مدیریتی آنها را برای استفاده‌های خاصی تعریف کند. حالت درختی آن مشخص است. یکسری از خصوصیات مربوط به محتوای صفحه و همچنین تنظیمات سئو برای آن در نظر گرفته شده است که بیشتر آنها در مقالات قبل توضیح داده شده‌اند. خصوصیت Section از نوع ShowPageSection و برای مشخص کردن امکان نمایش صفحه‌ی مورد نظر در نظر گرفته شده‌است. همچنین این مدل بالا از کلاس پایه‌ی مطرح شده‌ی در اول مقاله، ارث بری کرده است که امکان ردیابی تغییرات آن را مهیا می‌کند.
  خوب! حجم مقاله زیاد شده است و تا اینجا کافی خواهد بود ؛ بر خلاف تصور بنده، یک مقاله‌ی دیگر نیز برای اتمام بحث لازم میباشد.

نتیجه‌ی تا این قسمت

نظرات مطالب
نمایش پیام هشدار در Blazor با استفاده از کامپوننت Alert بوت استرپ ۵
روش دوم پیاده سازی این مثال: ارسال یک کامپوننت محصور کننده‌ی سراسری، به صورت پارامتر آبشاری، به تمام زیر کامپوننت‌ها

با استفاده از پارامترهای آبشاری می‌توان شیءای را در اختیار تمام کامپوننت‌های قرار گرفته شده‌ی در سلسله مراتب آن‌ها قرار داد. برای مثال اگر در فایل Client\Shared\MainLayout.razor، جائیکه سایر کامپوننت‌ها قرار است رندر شوند را توسط یک کامپوننت سطح بالا محصور کنیم:
<Alert>
  @Body
</Alert>
در این حالت هر کامپوننتی که بجای Body درج شود، می‌تواند به پارامترهای آبشاری Alert دسترسی پیدا کند؛ یعنی تمام کامپوننت‌های نمایشی برنامه. یا حتی می‌توان این Alert را در فایل Client\App.razor نیز در بالاترین سطح قرار داد و کل Router را توسط آن محصور کرد.
بنابراین طراحی ساده‌ی کامپوننت 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">&times;</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;
    }
}
که البته در همان پوشه به همراه فایل Client\Shared\AlertType.cs نیز هست:
namespace BlazorWasmAlert.Client.Shared
{
    public enum AlertType
    {
        Success,
        Info,
        Danger,
        Warning
    }
}
در کامپوننت ویژه‌ی Alert، دو قابلیت استاندارد Blazor بکار گرفته شده‌اند:
الف) وجود یک CascadingValue که اینبار Value آن به خود کامپوننت اشاره می‌کند (Value=this). یعنی پارامتر آبشاری که در اختیار سایر کامپوننت‌های محصور شده‌ی توسط آن ارسال می‌شود، دقیقا وهله‌ای از کامپوننت Alert است که توسط آن می‌توان برای مثال، متد عمومی ShowAlert آن‌را فراخوانی کرد:
<CascadingValue Value=this>
ب) چون کامپوننت Alert قرار است کامپوننت‌هایی را که بجای body@ درج می‌شوند را نمایش دهد، اینکار را توسط یک RenderFragment انجام داده‌است.


پس از درج این کامپوننت در فایل 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);
    }
}
در اینجا پارامتر آبشاری که دریافت می‌شود، دقیقا به کلاس و شیء Alert (وهله‌ای از کامپوننت Alert) اشاره می‌کند. به همین جهت می‌توان متد عمومی ShowAlert آن‌را در اینجا فراخوانی کرد.


پ.ن.
در طراحی 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.

 
تولید محتوای پویا در Angular
اشتراک‌ها
کتابخانه های برنامه های Front-End و معماری کامپوننت ها

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. 

کتابخانه های برنامه های Front-End و معماری کامپوننت ها
نظرات مطالب
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
- فلسفه‌ی کار با Blazor server، امکان دسترسی مستقیم به لایه سرویس‌های برنامه، بدون نیاز به طراحی یک Web API خاص آن‌ها است (کار با آن ساده‌تر است). اگر قرار است با Web API کار کنید، شاید بهتر باشد از WASM استفاده کنید.
- سرویس‌های Blazor Server، طول عمر خاصی دارند و تا زمانیکه اتصال برقرار است، از دست نمی‌روند. بنابراین خیلی از اطلاعات را می‌توان به صورت متداولی در آن‌ها ذخیره کرد و نیازی به تمهید خاصی نیست؛ هرچند encrypted local storage هم دارند.
- امکان طراحی interceptor برای HTTP Client هم وجود دارد تا هربار نیازی به مقدار دهی هدر Authorization نباشد.
- بله. در این سری اگر از Identity استفاده شده، بیشتر هدف مدیریت کاربران بوده و یا برای Blazor Server، دسترسی به کوکی خودکار پس از لاگین. نکات تهیه‌ی authentication provider سفارشی مطرح شده‌ی در قسمت wasm این سری برای کار با JWT، همه جا یکسان است و وابستگی به Identity و یا حتی Identity server پیش‌فرض مطرح شده‌ی توسط مایکروسافت، ندارد.
نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
امکان رندر کامل یک کامپوننت Blazor توسط کدهای جاوااسکریپتی در Blazor 6x

مرحله‌ی اول آماده سازی یک کامپوننت، جهت دسترسی به آن توسط کدهای جاوااسکریپتی، ثبت آن به نحو زیر در فایل Program.cs است:
builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
البته نمونه‌ی blazor server آن به صورت زیر است:
builder.Services.AddServerSideBlazor(options =>
{
    options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
});
پس از آن، کدهای جاوااسکریپتی فراخوان کامپوننت 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>
که نتیجه‌ی نهایی این فراخوانی، در div ای با id مساوی my-counter، رندر خواهد شد. در اینجا نحوه‌ی ارسال پارامتری را نیز به این کامپوننت، مشاهده می‌کنید.
نظرات مطالب
سازماندهی برنامه‌های Angular توسط ماژول‌ها
چند نکته‌ی تکمیلی در مورد بهبود تعاریف Shared Module و Core Module

الف) چگونه از 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.");
    }
  }
};
روش کار به این صورت است که خود CoreModule را به سازنده‌ی همان کلاس تزریق می‌کنیم! اگر وهله‌ای از آن قابل دسترسی بود، یعنی Angular پیشتر این ماژول را import کرده‌است. در این حالت با صدور خطایی این مشکل را گوشزد می‌کنیم.
از همین روش برای تشخیص 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`. */]
    };
  }
};
متد forRoot به صورت استاتیک تعریف می‌شود و همچنین خروجی از نوع ModuleWithProviders دارد. توسط ModuleWithProviders سبب خواهیم شد، AppModule، این ماژول را به همراه آرایه‌ی providers آن import کند؛ اما سایر ماژول‌ها خیر.
سایر ماژول‌ها چون دسترسی به آرایه‌ی حذف شده‌ی 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
مطالب
تبدیل زیرنویس‌های خاص پلورال‌سایت به فرمت SRT - قسمت دوم
قسمت اول این مطلب را در اینجا می‌توانید مطالعه کنید. از سه سال قبل تا به این تاریخ، فرمت زیرنویس‌های این سایت به صورت JSON تغییر پیدا کرده‌است و یک چنین ساختار جدیدی را دارد:
{
    "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; }
}
و بعد از تشکیل این ساختار که توسط منوی edit و گزینه‌ی paste special ویژوال استودیو تشکیل شده‌است:


بارگذاری آن توسط کتابخانه‌ی JSON.NET به صورت ذیل خواهد بود:
public static PluralsightCourse ProcessJsonFile(string jsonData)
{
    return JsonConvert.DeserializeObject<PluralsightCourse>(jsonData);
}
و پس از آن اگر حلقه‌ای را بر روی ماژول‌ها و سپس آیتم‌های هر ماژول تشکیل دهیم، می‌توان فرمت آن‌را به فرمت استاندارد srt که قابلیت پخش در اکثر برنامه‌های video player را دارد (مانند kmplayer)، تبدیل کرد.


کدهای کامل این برنامه را از اینجا می‌توانید دریافت کنید:
PluralsightJsonTranscripts.V1.0.7z