اشتراک‌ها
کارآیی JavaScript بر روی گوشی‌های اندرویدی، 7 برابر کمتر از نمونه‌ی iOS ای است

This is just terrible for the web. When there's a 7x difference between the JS performance on a BRAND NEW PIXEL and a new iPhone, web app makers just have to approach the whole game differentially. I mean, the Pixel 5 is FIVE YEARS behind the performance game. 

کارآیی JavaScript بر روی گوشی‌های اندرویدی، 7 برابر کمتر از نمونه‌ی iOS ای است
اشتراک‌ها
تغییرات ASP.NET Core و Blazor در NET Core 3.0 Preview 6.

Here’s the list of what’s new in this preview:

  • New Razor features: @attribute, @code, @key, @namespace, markup in @functions
  • Blazor directive attributes
  • Authentication & authorization support for Blazor apps
  • Static assets in Razor class libraries
  • Json.NET no longer referenced in project templates
  • Certificate and Kerberos Authentication
  • SignalR Auto-reconnect
  • Managed gRPC Client
  • gRPC Client Factory
  • gRPC Interceptors 
تغییرات ASP.NET Core و Blazor در NET Core 3.0 Preview 6.
نظرات مطالب
React 16x - قسمت 29 - احراز هویت و اعتبارسنجی کاربران - بخش 4 - محافظت از مسیرها
- می‌توان دسترسی داشت. پس از لاگین، برنامه را در چند برگه‌ی مجزا باز کنید، باز هم کار می‌کند و کاربر جاری تشخیص داده می‌شود. این محدودیت دسترسی مربوط به دومین جاری برنامه است و نه برگه‌های آن (مفهوم پیاده سازی sand box یا قرنطینه‌ی امنیتی).
- در مورد جزئیات محل‌های ذخیره سازی:
- در مورد علت استفاده‌ی از local storage و مزایا و معایب آن (نگاهی به محل ذخیره سازی JWT و نکات مرتبط با آن ) در اینجا : معرفی JSON Web Token
اشتراک‌ها
فعال سازی browser cache در ASP.NET MVC

The OutputCache attribute does not allow us to dynamically change the cache settings for the current request. As MVC and Web API both rely on different libraries I could not reuse the Web API action filter in MVC so I had to come up with a new solution. This again resulted in two simple classes. 

فعال سازی browser cache در ASP.NET MVC
مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت اول
قصد داریم طی یک سری مقالات به توسعه یک سیستم مدیریت محتوا بپردازیم. مسلما فاصله‌ی زمانی بین انتشار مقالات این سری، کمی زیاد خواهد بود. ولی سعی خواهیم کرد تا قدم به قدم و با تحلیل و توضیح کافی هر بخش به این هدف برسیم.
همکاران این قسمت:
پیشنیاز‌ها:
در زیر بخش هایی که برای این سیستم تا به امروز در نظر گرفتیم (مسلما با ارائه ایده‌ها و بازخورد‌های دوستان این امکانات دستخوش تغییر خواهند شد) ، به شرح زیر میباشد:
  • انجمن
  • ارتباط دوستی
  • سیستم ترفیع رتبه
  • Themeable
  • سیستم Following
  • صفحات داینامیک
  • سیستم پیام رسانی
  • امکان ساخت گروه‌های شخصی برای انتشار مطالب خود (توسط کاربران) با اعمال دسترسی مختلف
  • پیغام خصوصی
  • وبلاگ
  • نظرسنجی ها
  • مدیریت کاربران با دسترسی‌ها داینامیک
  • اخبار
  • آگهی ها
در این قسمت مدل‌های مربوط به بخش وبلاگ را بررسی میکنیم. ابتدا قصد داشتیم تا امکان نگارش یک پست توسط چندین کاربر را به طور همزمان، در سیستم قرار دهیم و ایده کار هم که توسط یکی دوستان (محمد شریفی) پیشنهاد شد به صورت زیر بود:
ابتدا کاربری به عنوان ایجاد کننده اصلی پست، بخش‌ها مختلفی را برای یک پست در نظر بگیرد و هر قسمت را به یک همکار نسبت دهد و با اتمام تمام بخش‌ها و اعلام آن توسط همکاران، کاربر اصلی ایجاد کننده پست بتواند بخش‌ها را باهم ادغام کند و برای آماده انتشار نهایی باشد.
ولی خب به نظر بنده الان سیستم‌های ارتباطی قدرتمندتری هم هست که همکاران در مورد نگارش یک پست به صورت مشارکتی بپردازند و صرفا در مرحله‌ی انتشار، این کاربران به عنوان کاربران همکار به پست منتشر شده نسبت داده شوند تا در زیر پست مشخصات کامل آنها قابل مشاهد باشد (راه حلی که پیاده سازی خواهیم کرد).
مدل‌های در نظر گرفته شده برای سیستم وبلاگ به صورت زیر می‌باشد:

مخزن برچسب ها
/// <summary>
    /// Represents the lable 
    /// </summary>
    public class Tag
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="Tag"/>
        /// </summary>
        public Tag()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets Tag's identifier
        /// </summary>
        public virtual Guid  Id { get; set; }
        /// <summary>
        /// sets or gets Tag's name
        /// </summary>
        public virtual string Name { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets Tag's posts
        /// </summary>
        public virtual ICollection<BlogPost> BlogPosts { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی مخزن برچسب‌های سیستم ما خواهد بود. جدول حاصل از این مدل با جداول سایر بخش‌ها که نیاز به داشتن برچسب خواهند داشت، ارتباط چند به چند خواهد داشت. برای این منظور همانطور که مشخص است در قسمت NavigationProperties یک لیست از BlogPost معرفی شده است و در ادامه خواهیم دید که لیستی از کلاس Tag هم در کلاس BlogPost معرفی خواهد شد.

مدل پیش نویس ها
  /// <summary>
    /// Represents the Post's Draft
    /// </summary>
    public  class BlogDraft
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="BlogDraft"/>
        /// </summary>
        public  BlogDraft()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of post's draft
        /// </summary>
        public virtual  Guid Id { get; set; }
        /// <summary>
        /// gets or sets body of post's draft
        /// </summary>
        public virtual  string Body { get; set; }
        /// <summary>
        /// gets or set title of post's draft
        /// </summary>
        public virtual  string Title { get; set; }
        /// <summary>
        /// gets or sets tags of post's draft that seperated using ','
        /// </summary>
        public virtual  string TagNames { get; set; }
        /// <summary>
        /// gets or sets value indicating whether this draft is ready to publish
        /// </summary>
        public virtual  bool IsReadyForPublish { get; set; }
        /// <summary>
        /// ges ro sets DateTime that this draft added
        /// </summary>
        public virtual  DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets date that this draft publish as ready
        /// </summary>
        public virtual DateTime? ReadyForPublishOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Id of user that he is owner of this draft
        /// </summary>
        public virtual  long OwnerId { get; set; }
        /// <summary>
        /// gets or sets user that he is owner of this draft
        /// </summary>
        public virtual  User Owner { get; set; }
        #endregion
    }
کلاس فوق برای ذخیره سازی پست‌های پیشنویس کاربران در نظر گرفته شده است. شاید بهتر بود این پیش نویس‌ها هم در همان مدل پستی که در ادامه مشاهده می‌کنید ادغام شوند. ولی این یک تصمیم شخصی برای این کار بوده و برای سیستی بزرگی که امکان دارد حذف و درج پیشنویس‌ها زیاد باشد و فرض بر اینکه long هم برای آی دی جواب گو نخواهد بود و نیاز است از Guid ای که به صورت متوالی افزایش میابد به منظور جلوگیری از Fragmentation، استفاده کرد. بقیه فیلد‌ها هم مشخص هستند و نیازی به توضیح اضافی نخواهد بود. مسلما هر کاربر می‌تواند چندین پیشنویس داشته باشد. لذا ارتباط یک به چند مابین کاربر و پیشنویس پست، خواهیم داشت.
مدل امتیاز دهی کاربران
/// <summary>
    /// Section of Rating
    /// </summary>
    public enum RatingSection
    {
        News,
        Announcement,
        ForumTopic,
        BlogComment,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        ...
    }


    /// <summary>
    /// Represents Rating Record regard by section type for Rating System
    /// </summary>
    public class UserRating
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="UserRating"/>
        /// </summary>
        public UserRating()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of Rating Record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets value of rate
        /// </summary>
        public virtual double RatingValue { get; set; }
        /// <summary>
        /// gets or sets Section's Id 
        /// </summary>
        public virtual long SectionId { get; set; }
        /// <summary>
        /// gets or sets Section 
        /// </summary>
        public virtual RatingSection Section { get; set; }
        #endregion

        #region Navigation Properties
        /// <summary>
        /// gets or sets user that rate one section
        /// </summary>
        public virtual User Rater { get; set; }
        /// <summary>
        /// gets or sets Rater Id that rate one section
        /// </summary>
        public virtual long RaterId { get; set; }

        #endregion
    }
نوع داده‌ی شمارشی RatingSection مشخص کننده‌ی بخش‌های مختلف سیستم می‌باشد که نیاز به امتیاز دهی دارند. چون سیستم ما یک سیستم بسته می‌باشد و برای امتیاز دهی نیاز به احراز هویت خواهد بود، لذا باید از امتیاز دادن بیش از یک بار برای هر بخش جلوگیری کنیم. برای این کار می‌توان بین تمام بخش‌های موجود ارتباط‌های چند به چند در نظر گرفت. برای مثال یک ارتباط چند به چند بین کاربر و نظرات که مشخص کننده‌ی این است که یک کاربر می‌تواند به چند نظر امتیاز دهد و هر نظر هم میتواند چندین امتیاز دهنده داشته باشد ولی تعداد جداول بالا خواهد رفت و کار آن هم زیاد خواهد شد. برای این منظور میتوان یک جدول به مانند UserRating در نظر گرفت که صرفا با جدول کاربر ما یک ارتباط یک به چند دارد و با استفاده از خصوصیت RatingSection موجود در این کلاس وخصوصیت SectionId و همچنین در نظر گرفتن یک کلید منحصر به فرد بر روی خصوصیت‌های RatingSection ، RaterId و SectionId، از امتیاز دادن چند باره‌ی کاربر به یک بخش جلوگیری کرد.
/// <summary>
    /// Represent the rating as ComplexType
    /// </summary>
    [ComplexType]
    public class Rating
    {
        /// <summary>
        /// sets or gets total of rating
        /// </summary>
        public virtual double? TotalRating { get; set; }
        /// <summary>
        /// sets or gets rater's count
        /// </summary>
        public virtual long? RatersCount { get; set; }
        /// <summary>
        /// sets or gets average of rating
        /// </summary>
        public virtual double? AverageRating { get; set; }
    }
کلاس بالا یک ComplexType می‌باشد که در نهایت به هیچ جدولی مپ نخواهد شد و صرفا به منظور کپسوله کردن یکسری فیلد مورد استفاده قرار گرفته است و از این کلاس می‌توان در هر بخش که لازم است امتیاز دهی داشته باشیم، به عنوان یک خصوصیت، استفاده کرد.

کلاس پایه‌ی محتوا
/// <summary>
    /// Represents a base class for every content in system
    /// </summary>
    public abstract class BaseContent
    {
        #region Properties

        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of publishing content
        /// </summary>
        public virtual DateTime PublishedOn { get; set; }
        /// <summary>
        /// gets or sets Last Update's Date
        /// </summary>
        public virtual DateTime ModifiedOn { get; set; }
        /// <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 a value indicating whether the content comments are allowed 
        /// </summary>
        public virtual bool AllowComments { get; set; }
        /// <summary>
        /// gets or sets a value indicating whether the content comments are allowed for anonymouses 
        /// </summary>
        public virtual bool AllowCommentForAnonymous { get; set; }
        /// <summary>
        /// gets or sets  viewed count by rss
        /// </summary>
        public virtual long ViewCountByRss { get; set; }
        /// <summary>
        /// gets or sets viewed count 
        /// </summary>
        public virtual long ViewCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.Approved).Count()
        /// We use this property for performance optimization (no SQL command executed)
        /// </remarks>
        /// </summary>
        public virtual int ApprovedCommentsCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.UnApproved).Count()
        /// We use this property for performance optimization (no SQL command executed)</remarks></summary>
        public virtual int UnApprovedCommentsCount { get; set; }
        /// <summary>
        /// gets or sets value  indicating whether the content is logical deleted or hidden
        /// </summary>
        public virtual bool IsDeleted { get; set; }
        /// <summary>
        /// gets or sets rating complex instance
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content show with rssFeed
        /// </summary>
        public virtual bool ShowWithRss { get; set; }
        /// <summary>
        /// gets or sets value indicating maximum days count that users can send comment
        /// </summary>
        public virtual int DaysCountForSupportComment { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets icon name with size 200*200 px for snippet 
        /// </summary>
        public virtual string SocialSnippetIconName { get; set; }
        /// <summary>
        /// gets or sets title for snippet
        /// </summary>
        public virtual string SocialSnippetTitle { get; set; }
        /// <summary>
        /// gets or sets description for snippet
        /// </summary>
        public virtual string SocialSnippetDescription { get; set; }
        /// <summary>
        /// gets or sets body of content's comment
        /// </summary>
        public virtual byte[] RowVersion { get; set; }
        /// <summary>
        /// gets or sets name of tags seperated by comma that assosiated with this content fo increase performance
        /// </summary>
        public virtual string TagNames { get; set; }
        /// <summary>
        /// gets or sets counter for Content's report
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Author { get; set; }

        /// <summary>
        /// gets or sets Id of user that create this record
        /// </summary>
        public virtual long AuthorId { get; set; }
        /// <summary>
        /// get or set  the tags integrated with content
        /// </summary>
        public virtual ICollection<Tag> Tags { get; set; }
        #endregion

    }

بخش‌های مختلفی که در ابتدای مقاله مطرح شدند، دارای یکسری خصوصیات مشترک می‌باشند و برای این منظور این خصوصیات را در یک کلاس پایه کپسوله کرده‌ایم. شاید تفکر شما این باشد که میخواهیم ارث بری TPH یا TPT را اعمال کنیم. ولی با توجه به سلیقه‌ی شخصی، در این بخش قصد استفاده از ارث بری را ندارم. 

نکته‌ای که وجود دارد فیلد‌های ApprovedCommentsCount  UnApprovedCommentsCount و TagNames می‌باشند که هنگام درج نظر جدید باید تعداد نظرات ذخیره شده را  ویرایش کنیم و هنگام ویرایش خود پست یا خبر با ... و یا حتی ویرایش خود تگ یا حذف آن تگ باید TagNames که لیست برچسب‌های محتوا را به صورت جدا شده با (,) از هم دیگر می‌باشد، ویرایش کنیم (جای بحث دارد).

مشخص است که هر یک از مطالب منتشر شده در بخش‌های وبلاگ، اخبار، نظرسنجی و آگهی‌ها، یک کابر ایجاد کننده (Author نامیده‌ایم) خواهد داشت و هر کاربر هم می‌تواند چندین مطلب را ایجاد کند. لذا رابطه‌ی یک به چند بین تمام این بخش‌ها مذکور و کاربر ایجاد خواهد شد.

مدل  LinkBack

 /// <summary>
    /// Represents link for implemention linkback
    /// </summary>
    public class LinkBack
    {

        #region Ctor
        /// <summary>
        /// create one instance of <see cref="LinkBack"/>
        /// </summary>
        public LinkBack()
        {
            CreatedOn = DateTime.Now;
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets link's Id
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets text for show Link
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets link's address 
        /// </summary>
        public virtual string Url { get; set; }
        /// <summary>
        /// gets or set value indicating whether this link is internal o external
        /// </summary>
        public virtual LinkBackType Type { get; set; }
        /// <summary>
        /// gets or sets date that this record is added
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Post that associated
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets id of Post that associated
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }


 /// <summary>
    /// represents Type of ReferrerLinks
    /// </summary>
    public enum LinkBackType
    {
        /// <summary>
        /// Internal link
        /// </summary>
        Internal,
        /// <summary>
        /// External Link
        /// </summary>
        External
    }

مطمئنا در خیلی از وبلاگ‌ها مثل سایت جاری متوجه نمایش لینک‌ها ارجاع دهنده‌های خارجی و داخلی در زیر مطلب شده‌اید. کلاس LinkBack هم دقیقا برای این منظور در نظرگرفته شده است که عنوان صفحه‌ای که این پست در آنجا لینک داده شده است، به همراه آدرس آن صفحه، در جدول حاصل از این کلاس ذخیره خواهند شد. نوع داده LinkBackType هم برای متمایز کردن رکورد‌های درج شده به عنوان LinkBack در نظر گرفته شده است که بتوان آنها را متمایز کرد، به ارجاعات داخلی و خارجی.

مدل پست ها

  /// <summary> 
    /// Represents a blog post
    /// </summary>
    public  class BlogPost : BaseContent
    {
        #region Ctor
        /// <summary>
        /// Create one Instance of <see cref="BlogPost"/>
        /// </summary>
        public BlogPost()
        {
            Rating = new Rating();
            PublishedOn = DateTime.Now;
        }
        #endregion

       #region Properties
        /// <summary>
        /// gets or sets Status of LinkBack Notifications
        /// </summary>
        public virtual LinkBackStatus LinkBackStatus { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set  blog post's Reviews
        /// </summary>
        public virtual ICollection<BlogComment> Comments { get; set; }
        /// <summary>
        /// get or set collection of links that reference to this blog post
        /// </summary>
        public virtual ICollection<LinkBack> LinkBacks { get; set; }
        /// <summary>
        /// get or set Collection of Users that Contribute on this post
        /// </summary>
        public virtual ICollection<User> Contributors  { get; set; }
        
        #endregion
    }


  /// <summary>
    /// Represents Status for ReferrerLinks
    /// </summary>
    public enum  LinkBackStatus
    {
        [Display(Name ="غیرفعال")]
        Disable,
        [Display(Name = "فعال")]
        Enable,
        [Display(Name = "لینک‌ها داخلی")]
        JustInternal,
        [Display(Name = "لینک‌ها خارجی")]
        JustExternal
    }
 کلاس بالا نشان دهنده‌ی مدل پست‌های ما در سیستم می‌باشد که بیشتر خصوصیات خود را از کلاس پایه‌ی BaseContent به ارث برده و لازم به تکرار آنها نیست. به علاوه یکسری خصوصیات دیگر، از جلمه‌ی آنها یک لیست از کلاس LinkBack، لیستی از کاربران مشارکت کننده به منظور نمایش مشخصات آنها در زیر پست و لیستی از کلاس BlogComment که نشان دهنده‌ی نظرات پست می‌باشد، هم خواهد داشت. خصوصیتی از نوع داده‌ی LinkBackStatus هم صرفا به منظور تنظیمات مدیریتی در نظر گرفته شده است. 

کلاس پایه نظرات
  /// <summary>
    /// Represents a base class for every comment in system 
    /// </summary>

    public abstract class BaseComment
    {
        #region Properties
        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of creation 
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets displayName of this comment's Creator if  he/she is Anonymous
        /// </summary>
        public virtual string CreatorDisplayName { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets informations of agent
        /// </summary>
        public virtual string UserAgent { get; set; }
        /// <summary>
        /// gets or sets siteUrl of Creator if he/she is Anonymous
        /// </summary>
        public virtual string SiteUrl { get; set; }
        /// <summary>
        /// gets or sets Email of Creator if he/she is anonymous
        /// </summary>
        public virtual string Email { get; set; }
        /// <summary>
        /// gets or sets status of comment
        /// </summary>
        public virtual CommentStatus Status { get; set; }
        /// <summary>
        /// gets or sets Ip Address of Creator
        /// </summary>
        public virtual string CreatorIp { get; set; }
        /// <summary>
        /// gets or sets datetime that is modified
        /// </summary>
        public virtual DateTime? ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets counter for report this comment
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Creator { get; set; }

        /// <summary>
        /// get or set Id of user that create this record
        /// </summary>
        public virtual long? CreatorId { get; set; }
        #endregion
    }

public enum CommentStatus
    {
        /* 0 - approved, 1 - pending, 2 - spam, -1 - trash */
        [Display(Name = "تأیید شده")]
        Approved = 0,
        [Display(Name = "در انتظار بررسی")]
        Pending = 1,
        [Display(Name = "جفنگ")]
        Spam = 2,
        [Display(Name = "زباله دان")]
        Trash = -1
    }
به مانند BaseContent که برای کپسوله کردن خصوصیات تکراری و نه برای اعمال ارث بری TPH یا TPT، کلاس BaseComment را در نظر گرفته‌ایم. نکته‌ی مهم این است که هر نظری یک درج کننده دارد. ولی اگر فیلد AllowCommentForAnonymous مربوط به مطلب True باشد، این امکان وجود خواهد داشت تا کاربران احراز هویت نشده نیز نظر ارسال کنند. لذا CreatorId در کلاس BaseComment به صورت Nullable در نظر گرفته شده است و یک سری خصوصیت‌ها از قبیل SiteUrl ، CreatorDisplayName ، Email برای کاربران احراز هویت نشده در نظر گرفته شده است. نوع شمارشی CommentStatus نیز برای اعمال مدیریتی در نظر گرفته شده است.
مدل نظرات پست ها
/// <summary>
    /// Represents a blog post's comment
    /// </summary>
    public class BlogComment : BaseComment
    {
        #region Ctor
        /// <summary>
        /// Create One Instance for <see cref="BlogComment"/>
        /// </summary>
        public BlogComment()
        {
            Rating = new Rating();
            CreatedOn = DateTime.Now;
        }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets BlogComment's identifier for Replying and impelemention self referencing
        /// </summary>
        public virtual long? ReplyId { get; set; }
        /// <summary>
        /// gets or sets blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual BlogComment Reply { get; set; }
        /// <summary>
        /// get or set collection of blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual ICollection<BlogComment> Children { get; set; }
        /// <summary>
        /// gets or sets post that this comment sent to it
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets post'Id that this comment sent to it
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی نظرات پست‌های وبلاگ می‌باشند که بیشتر خصوصیت‌های خود را از کلاس پایه‌ی BaseComment به ارث برده است و همچنین ساختار سلسله مراتبی آن نیز  قابل مشاهده است. 
برای سیستم Report یا همان گزارش خطا هم سیستمی شبیه به امتیاز دهی ستاره‌ای در نظر گرفته ایم. برای اینکه مقاله خیلی طولانی شد، بهتر است در مقاله‌ای جدا کار را ادامه داد.
نتیجه این قسمت

مطالب دوره‌ها
ایجاد کاتالوگ‌های Full text search و ایندکس‌های آن
جستجو بر روی خواص و متادیتای اسناد آفیس

همانطور که در قسمت قبل نیز عنوان شد، فیلترهای FTS آفیس، علاوه بر اینکه امکان جستجوی پیشرفته FTS را بر روی کلیه فایل‌های مجموعه آفیس میسر می‌کنند، امکان جستجوی FTS را بر روی خواص ویژه اضافی آن‌ها، مانند نام نویسنده، واژه‌های کلیدی، تاریخ ایجاد و امثال آن نیز به همراه دارند.
اینکه چه خاصیتی را بتوان جستجو کرد نیز بستگی به نوع فیلتر نصب شده دارد. برای تعریف خواص قابل جستجوی یک سند، باید یک SEARCH PROPERTY LIST را ایجاد کرد:
CREATE SEARCH PROPERTY LIST WordSearchPropertyList;
GO

ALTER SEARCH PROPERTY LIST WordSearchPropertyList
   ADD 'Authors'
   WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9',
             PROPERTY_INT_ID = 4,
             PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.');
GO
در این تعریف، PROPERTY_INT_ID و PROPERTY_SET_GUIDها استاندارد بوده و لیست آن‌ها را در آدرس ذیل می‌توانید مشاهده نمائید:


بهبود کیفیت جستجو توسط Stop lists و Stop words

به یک سری از کلمات و حروف، اصطلاحا noise words گفته می‌شود. برای مثال در زبان انگلیسی حروف و کلماتی مانند a، is، the و and به صورت خودکار از FTS حذف می‌شوند؛ چون جستجوی آن‌ها بی‌حاصل است. به این‌ها stop words نیز می‌گویند.
با استفاده از کوئری ذیل می‌توان لیست stop words تعریف شده در بانک اطلاعاتی جاری را مشاهده کرد:
 -- Check the Stopwords list
SELECT w.stoplist_id,
   l.name,
   w.stopword,
   w.language
FROM sys.fulltext_stopwords AS w
   INNER JOIN sys.fulltext_stoplists AS l
     ON w.stoplist_id = l.stoplist_id;
و برای تعریف stop words از دستورات ذیل کمک گرفته می‌شود:
 -- Stopwords list
CREATE FULLTEXT STOPLIST SQLStopList;
GO

-- Add a stopword
ALTER FULLTEXT STOPLIST SQLStopList
 ADD 'SQL' LANGUAGE 'English';
GO


کاتالوگ‌های Full Text Search

ایندکس‌های ویژه‌ی FTS، در مکان‌هایی به نام Full Text Catalogs ذخیره می‌شوند. این کاتالوگ‌ها صرفا یک شیء مجازی بوده و تنها برای تعریف ظرفی دربرگیرنده‌ی ایندکس‌های FTS تعریف می‌شوند. در نگارش‌های پیش از 2012 اس کیوال سرور، این کاتالوگ‌ها اشیایی فیزیکی بودند؛ اما اکنون تبدیل به اشیایی مجازی شده‌اند.
حالت کلی تعریف یک fulltext catalog به نحو ذیل است:
 create fulltext catalog catalog_name
on filegroup filegroup_name
in path  'rootpath'
with some_options
as default
authoriztion owner_name
accent_sensivity = {on|off}
اما اکثر گزینه‌های آن مانند on filegroup و in path صرفا برای حفظ سازگاری با نگارش‌های قبلی حضور دارند و دیگر نیازی به ذکر آن‌ها نیست؛ چون تعریف کننده‌ی ماهیت فیزیکی این کاتالوگ‌ها می‌باشند.
به صورت پیش فرض حساسیت به لهجه یا accent_sensivity خاموش است. اگر روشن شود، باید کل ایندکس مجددا بازسازی شود.


ایجاد ایندکس‌های Full Text

پس از ایجاد یک fulltext catalog، اکنون نوبت به تعریف ایندکس‌هایی فیزیکی هستند که داخل این کاتالوگ‌ها ذخیره خواهند شد:
 -- Full-text catalog
CREATE FULLTEXT CATALOG DocumentsFtCatalog;
GO

-- Full-text index
CREATE FULLTEXT INDEX ON dbo.Documents
(
  docexcerpt Language 1033,
  doccontent TYPE COLUMN doctype
  Language 1033
  STATISTICAL_SEMANTICS
)
KEY INDEX PK_Documents
ON DocumentsFtCatalog
WITH STOPLIST = SQLStopList,
  SEARCH PROPERTY LIST = WordSearchPropertyList,
  CHANGE_TRACKING AUTO;
GO
در اینجا توسط KEY INDEX نام منحصربفرد ایندکس مشخص می‌شود.
CHANGE_TRACKING AUTO به این معنا است که SQL Server به صورت خودکار کار به روز رسانی این ایندکس را با تغییرات رکوردها انجام خواهد داد.
ذکر STATISTICAL_SEMANTICS، منحصر به SQL Server 2012 بوده و کار آن تشخیص واژه‌های کلیدی و ایجاد ایندکس‌های یافتن اسناد مشابه است. برای استفاده از آن حتما نیاز است مطابق توضیحات قسمت قبل، Semantic Language Database پیشتر نصب شده باشد.
توسط STOPLIST، لیست واژه‌هایی که قرار نیست ایندکس شوند را معرفی خواهیم کرد. SQLStopList را در ابتدای بحث ایجاد کردیم.
Language 1033 به معنای استفاده از زبان US English است.
نحوه‌ی استفاده از SEARCH PROPERTY LIST ایی که پیشتر تعریف کردیم را نیز در اینجا ملاحظه می‌کنید.


مثالی برای ایجاد ایندکس‌های FTS

برای اینکه ربط منطقی نکات عنوان شده را بهتر بتوانید بررسی و آزمایش کنید، مثال ذیل را درنظر بگیرید.
ابتدا جدول Documents را برای ذخیره سازی تعدادی سند، ایجاد می‌کنیم:
 CREATE TABLE dbo.Documents
(
  id INT IDENTITY(1,1) NOT NULL,
  title NVARCHAR(100) NOT NULL,
  doctype NCHAR(4) NOT NULL,
  docexcerpt NVARCHAR(1000) NOT NULL,
  doccontent VARBINARY(MAX) NOT NULL,
  CONSTRAINT PK_Documents
PRIMARY KEY CLUSTERED(id)
);
اگر به این جدول دقت کنید، هدف از آن ذخیره‌ی اسناد آفیس است که فیلترهای FTS آن‌را در قسمت قبل نصب کردیم. ستون doctype، معرف نوع سند و doccontent ذخیره کننده‌ی محتوای کامل سند خواهند بود.

سپس اطلاعاتی را در این جدول ثبت می‌کنیم:
-- Insert data
-- First row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Columnstore Indices and Batch Processing', 
 N'docx',
 N'You should use a columnstore index on your fact tables,
   putting all columns of a fact table in a columnstore index. 
   In addition to fact tables, very large dimensions could benefit 
   from columnstore indices as well. 
   Do not use columnstore indices for small dimensions. ',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\ColumnstoreIndicesAndBatchProcessing.docx', 
  SINGLE_BLOB) AS doc;

-- Second row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Introduction to Data Mining', 
 N'docx',
 N'Using Data Mining is becoming more a necessity for every company 
   and not an advantage of some rare companies anymore. ',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\IntroductionToDataMining.docx', 
  SINGLE_BLOB) AS doc;

-- Third row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Why Is Bleeding Edge a Different Conference', 
 N'docx',
 N'During high level presentations attendees encounter many questions. 
   For the third year, we are continuing with the breakfast Q&A session. 
   It is very popular, and for two years now, 
   we could not accommodate enough time for all questions and discussions! ',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\WhyIsBleedingEdgeADifferentConference.docx', 
  SINGLE_BLOB) AS doc;

-- Fourth row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Additivity of Measures', 
 N'docx',
 N'Additivity of measures is not exactly a data warehouse design problem. 
   However, you have to realize which aggregate functions you will use 
   in reports for which measure, and which aggregate functions 
   you will use when aggregating over which dimension.',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\AdditivityOfMeasures.docx', 
  SINGLE_BLOB) AS doc;
GO
4 ردیف ثبت شده در جدول اسناد، نیاز به 4 فایل docx نیز دارند که آن‌ها را از آدرس ذیل می‌توانید برای تکمیل ساده‌تر آزمایش دریافت کنید:
fts_docs.zip

در ادامه می‌خواهیم قادر باشیم تا بر روی متادیتای نویسنده‌ی این اسناد نیز جستجوی کامل FTS را انجام دهیم. به همین جهت SEARCH PROPERTY LIST آن‌را نیز ایجاد خواهیم کرد:
 -- Search property list
CREATE SEARCH PROPERTY LIST WordSearchPropertyList;
GO
ALTER SEARCH PROPERTY LIST WordSearchPropertyList
 ADD 'Authors'
 WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9',
PROPERTY_INT_ID = 4,
PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.');
GO
همچنین می‌خواهیم از واژه‌ی SQL در این اسناد، در حین ساخت ایندکس‌های FTS صرفنظر شود. برای این منظور یک FULLTEXT STOPLIST را به نام SQLStopList ایجاد کرده و سپس واژه‌ی مدنظر را به آن اضافه می‌کنیم:
 -- Stopwords list
CREATE FULLTEXT STOPLIST SQLStopList;
GO
-- Add a stopword
ALTER FULLTEXT STOPLIST SQLStopList
 ADD 'SQL' LANGUAGE 'English';
GO
صحت عملیات آن‌را توسط کوئری «Check the Stopwords list» ذکر شده در ابتدای بحث می‌توانید بررسی کنید.

اکنون زمان ایجاد یک کاتالوگ FTS است:
 -- Full-text catalog
CREATE FULLTEXT CATALOG DocumentsFtCatalog;
GO
با توجه به اینکه در نگارش‌های جدید SQL Server این کاتالوگ صرفا ماهیتی مجازی دارد، ساده‌ترین Syntax آن برای کار ما کفایت می‌کند.
و در آخر ایندکس FTS ایی را که پیشتر در مورد آن بحث کردیم، ایجاد خواهیم کرد:
 -- Full-text index
CREATE FULLTEXT INDEX ON dbo.Documents
(
  docexcerpt Language 1033,
  doccontent TYPE COLUMN doctype
  Language 1033
  STATISTICAL_SEMANTICS
)
KEY INDEX PK_Documents
ON DocumentsFtCatalog
WITH STOPLIST = SQLStopList,
  SEARCH PROPERTY LIST = WordSearchPropertyList,
  CHANGE_TRACKING AUTO;
GO



در این تصویر محل یافتن اجزای مختلف Full text search را در management studio مشاهده می‌کنید.


یک نکته‌ی تکمیلی
برای زبان فارسی نیز یک سری stop words وجود دارند. لیست آن‌ها را از اینجا می‌توانید دریافت کنید:
stopwords.sql
متاسفانه زبان فارسی جزو زبان‌های پشتیبانی شده توسط FTS در SQL Server نیست (نه به این معنا که نمی‌توان با آن کار کرد؛ به این معنا که برای مثال دستورات صرفی زبان را ندارد) و به همین جهت از زبان انگلیسی در اینجا استفاده شده‌است.
اشتراک‌ها
مقدمه ای کوتاه بر REST

Perhaps you’ve seen the term REST being thrown around lately and have been a little curious what it’s all about. If that’s the case, or you know a little about it but never used it, this article is for you. Today I’m going to give a small overview of what REST services are, and why it’s awesome. 

مقدمه ای کوتاه بر REST
مطالب
روش استفاده‌ی صحیح از HttpClient در برنامه‌های دات نت
اگر در کدهای خود قطعه کد ذیل را دارید:
using(var client = new HttpClient())
{
   // do something with http client
}
استفاده‌ی از using در اینجا، نه‌تنها غیرضروری و اشتباه است، بلکه سبب از کار افتادن زود هنگام برنامه‌ی شما با صدور استثنای ذیل خواهد شد:
 Unable to connect to the remote server
System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.


HttpClient خود را Dispose نکنید

کلاس HttpClient اینترفیس IDisposable را پیاده سازی می‌کند. بنابراین روش استفاده‌ی اصولی آن باید به صورت ذیل و با پیاده سازی خودکار رهاسازی منابع مرتبط با آن باشد:
using (var client = new HttpClient())
{
       var result = await client.GetAsync("http://example.com/");
}
اما در این حال فرض کنید به همین روش تعدادی درخواست را ارسال کرده‌اید:
for (int i = 0; i < 10; i++)
{
      using (var client = new HttpClient())
      {
            var result = await client.GetAsync("http://example.com/");
            Console.WriteLine(result.StatusCode);
      }
}
مشکل این روش، در ایجاد سوکت‌های متعددی است که حتی پس از بسته شدن برنامه نیز باز، باقی خواهند ماند:
  TCP    192.168.1.6:13996      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13997      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13998      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13999      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14000      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14001      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14002      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14003      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14004      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14005      93.184.216.34:http     TIME_WAIT
این یک نمونه‌ی خروجی برنامه‌ی فوق، توسط دستور netstat «پس از بسته شدن کامل برنامه» است.

بنابراین اگر برنامه‌ی شما تعداد زیادی کاربر دارد و یا تعداد زیادی درخواست را به روش فوق ارسال می‌کند، سیستم عامل به حد اشباع ایجاد سوکت‌های جدید خواهد رسید.
این مشکل نیز ارتباطی به طراحی این کلاس و یا زبان #C و حتی استفاده‌ی از using نیز ندارد. این رفتار، رفتار معمول سیستم عامل، با سوکت‌های ایجاد شده‌است. TIME_WAIT ایی را که در اینجا ملاحظه می‌کنید، به معنای بسته شدن اتصال از طرف برنامه‌ی ما است؛ اما سیستم عامل هنوز منتظر نتیجه‌ی نهایی، از طرف دیگر اتصال است که آیا قرار است بسته‌ی TCP ایی را دریافت کند یا خیر و یا شاید در بین راه تاخیری وجود داشته‌است. برای نمونه ویندوز به مدت 240 ثانیه یک اتصال را در این حالت حفظ خواهد کرد، که مقدار آن نیز در اینجا تنظیم می‌شود:
 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay]

بنابراین روش توصیه شده‌ی کار با HttpClient، داشتن یک وهله‌ی سراسری از آن در برنامه و عدم Dispose آن است. HttpClient نیز thread-safe طراحی شده‌است و دسترسی به یک شیء سراسری آن در برنامه‌های چند ریسمانی مشکلی را ایجاد نمی‌کند. همچنین Dispose آن نیز غیرضروری است و پس از پایان برنامه به صورت خودکار توسط سیستم عامل انجام خواهد شد.


تمام اجزای HttpClient به صورت Thread-safe طراحی نشده‌اند

تا اینجا به این نتیجه رسیدیم که روش صحیح کار کردن با HttpClient، نیاز به داشتن یک وهله‌ی Singleton از آن‌را در سراسر برنامه دارد و Dispose صریح آن، بجز اشباع سوکت‌های سیستم عامل و ناپایدار کردن تمام برنامه‌هایی که از آن سرویس می‌گیرند، حاصلی را به همراه نخواهد داشت. در این بین مطابق مستندات HttpClient، استفاده‌ی از متدهای ذیل این کلاس thread-safe هستند:
CancelPendingRequests
DeleteAsync
GetAsync
GetByteArrayAsync
GetStreamAsync
GetStringAsync
PostAsync
PutAsync
SendAsync
اما تغییر این خواص در کلاس HttpClient به هیچ عنوان thread-safe نبوده و در برنامه‌های چند ریسمانی و چند کاربری، مشکل ساز می‌شوند:
BaseAddress
DefaultRequestHeaders
MaxResponseContentBufferSize
Timeout
بنابراین در طراحی کلاس مدیریت کننده‌ی HttpClient برنامه‌ی خود نیاز است به ازای هر BaseAddress‌، یک HttpClient خاص آن‌را ایجاد کرد و HttpClientهای سراسری نمی‌توانند BaseAddress‌های خود را نیز به اشتراک گذاشته و تغییری را در آن ایجاد کنند.


استفاده‌ی سراسری و مجدد از HttpClient، تغییرات DNS را متوجه نمی‌شود

با طراحی یک کلاس مدیریت کننده‌ی سراسری HttpClient با طول عمر Singelton، به یک مشکل دیگر نیز برخواهیم خورد: چون در اینجا از اتصالات، استفاده‌ی مجدد می‌شوند، دیگر تغییرات DNS را لحاظ نخواهند کرد.
برای حل این مشکل، در زمان ایجاد یک HttpClient سراسری، به ازای یک BaseAddress مشخص، باید از ServicePointManager کوئری گرفته و زمان اجاره‌ی اتصال آن‌را دقیقا مشخص کنیم:
var sp = ServicePointManager.FindServicePoint(new Uri("http://thisisasample.com"));
sp.ConnectionLeaseTimeout = 60*1000; //In milliseconds
با این‌کار هرچند هنوز هم از اتصالات استفاده‌ی مجدد می‌شود، اما این استفاده‌ی مجدد، نامحدود نبوده و مدت معینی را پیدا می‌کند.


طراحی یک کلاس، برای مدیریت سراسری وهله‌های HttpClient‌

تا اینجا به صورت خلاصه به نکات ذیل رسیدیم:
- HttpClient باید به صورت یک وهله‌ی سراسری Singleton مورد استفاده قرار گیرد. هر وهله سازی مجدد آن 35ms زمان می‌برد.
- Dispose یک HttpClient غیرضروری است.
- HttpClient تقریبا thread safe طراحی شده‌است؛ اما تعدادی از خواص آن مانند BaseAddress‌  اینگونه نیستند.
- برای رفع مشکل اتصالات چسبنده (اتصالاتی که هیچگاه پایان نمی‌یابند)، نیاز است timeout آن‌را تنظیم کرد.

بنابراین بهتر است این نکات را در یک کلاس به صورت ذیل کپسوله کنیم:
using System;
using System.Collections.Generic;
using System.Net.Http;

namespace HttpClientTips
{
    public interface IHttpClientFactory : IDisposable
    {
        HttpClient GetOrCreate(
            Uri baseAddress,
            IDictionary<string, string> defaultRequestHeaders = null,
            TimeSpan? timeout = null,
            long? maxResponseContentBufferSize = null,
            HttpMessageHandler handler = null);
    }
}

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;

namespace HttpClientTips
{
    /// <summary>
    /// Lifetime of this class should be set to `Singleton`.
    /// </summary>
    public class HttpClientFactory : IHttpClientFactory
    {
        // 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the HttpClient more than
        // once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple
        // threads but only one of the objects succeeds in creating the HttpClient.
        private readonly ConcurrentDictionary<Uri, Lazy<HttpClient>> _httpClients =
                         new ConcurrentDictionary<Uri, Lazy<HttpClient>>();
        private const int ConnectionLeaseTimeout = 60 * 1000; // 1 minute

        public HttpClientFactory()
        {
            // Default is 2 minutes: https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.dnsrefreshtimeout(v=vs.110).aspx
            ServicePointManager.DnsRefreshTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
            // Increases the concurrent outbound connections
            ServicePointManager.DefaultConnectionLimit = 1024;
        }

        public HttpClient GetOrCreate(
           Uri baseAddress,
           IDictionary<string, string> defaultRequestHeaders = null,
           TimeSpan? timeout = null,
           long? maxResponseContentBufferSize = null,
           HttpMessageHandler handler = null)
        {
            return _httpClients.GetOrAdd(baseAddress,
                             uri => new Lazy<HttpClient>(() =>
                             {
                                 // Reusing a single HttpClient instance across a multi-threaded application means
                                 // you can't change the values of the stateful properties (which are not thread safe),
                                 // like BaseAddress, DefaultRequestHeaders, MaxResponseContentBufferSize and Timeout.
                                 // So you can only use them if they are constant across your application and need their own instance if being varied.
                                 var client = handler == null ? new HttpClient { BaseAddress = baseAddress } :
                                               new HttpClient(handler, disposeHandler: false) { BaseAddress = baseAddress };
                                 setRequestTimeout(timeout, client);
                                 setMaxResponseBufferSize(maxResponseContentBufferSize, client);
                                 setDefaultHeaders(defaultRequestHeaders, client);
                                 setConnectionLeaseTimeout(baseAddress, client);
                                 return client;
                             },
                             LazyThreadSafetyMode.ExecutionAndPublication)).Value;
        }

        public void Dispose()
        {
            foreach (var httpClient in _httpClients.Values)
            {
                httpClient.Value.Dispose();
            }
        }

        private static void setConnectionLeaseTimeout(Uri baseAddress, HttpClient client)
        {
            // This ensures connections are used efficiently but not indefinitely.
            client.DefaultRequestHeaders.ConnectionClose = false; // keeps the connection open -> more efficient use of the client
            ServicePointManager.FindServicePoint(baseAddress).ConnectionLeaseTimeout = ConnectionLeaseTimeout; // ensures connections are not used indefinitely.
        }

        private static void setDefaultHeaders(IDictionary<string, string> defaultRequestHeaders, HttpClient client)
        {
            if (defaultRequestHeaders == null)
            {
                return;
            }
            foreach (var item in defaultRequestHeaders)
            {
                client.DefaultRequestHeaders.Add(item.Key, item.Value);
            }
        }

        private static void setMaxResponseBufferSize(long? maxResponseContentBufferSize, HttpClient client)
        {
            if (maxResponseContentBufferSize.HasValue)
            {
                client.MaxResponseContentBufferSize = maxResponseContentBufferSize.Value;
            }
        }

        private static void setRequestTimeout(TimeSpan? timeout, HttpClient client)
        {
            if (timeout.HasValue)
            {
                client.Timeout = timeout.Value;
            }
        }
    }
}
در اینجا به ازای هر baseAddress جدید، یک HttpClient خاص آن ایجاد می‌شود تا در کل برنامه مورد استفاده‌ی مجدد قرار گیرد. برای مدیریت thread-safe ایجاد HttpClientها نیز از نکته‌ی مطلب «الگویی برای مدیریت دسترسی همزمان به ConcurrentDictionary» استفاده شده‌است. همچنین نکات تنظیم ConnectionLeaseTimeout و سایر خواص غیر thread-safe کلاس HttpClient نیز در اینجا لحاظ شده‌اند.

پس از تدارک این کلاس، نحوه‌ی معرفی آن به سیستم باید به صورت Singleton باشد. برای مثال اگر از ASP.NET Core استفاده می‌کنید، آن‌را به صورت ذیل ثبت کنید:
namespace HttpClientTips.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IHttpClientFactory, HttpClientFactory>();
            services.AddMvc();
        }

اکنون، یک نمونه، نحوه‌ی استفاده‌ی از اینترفیس IHttpClientFactory تزریقی به صورت ذیل می‌باشد:
namespace HttpClientTips.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IHttpClientFactory _httpClientFactory;
        public HomeController(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public async Task<IActionResult> Index()
        {
            var host = new Uri("http://localhost:5000");
            var httpClient = _httpClientFactory.GetOrCreate(host);
            var responseMessage = await httpClient.GetAsync("home/about").ConfigureAwait(false);
            var responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
            return Content(responseContent);
        }
سرویس IHttpClientFactory یک HttpClient را به ازای host درخواستی ایجاد کرده و در طول عمر برنامه از آن استفاده‌ی مجدد می‌کند. به همین جهت دیگر مشکل اشباع سوکت‌ها در این سیستم رخ نخواهند داد.


برای مطالعه‌ی بیشتر

You're using HttpClient wrong and it is destabilizing your software
Disposable, Finalizers, and HttpClient
Using HttpClient as it was intended (because you’re not)
Singleton HttpClient? Beware of this serious behaviour and how to fix it
Beware of the .NET HttpClient
Effectively Using HttpClient
اشتراک‌ها
به زودی در HTML6

The features include:

  • Express Tags
  • Native Modals Support
  • Freedom to Resize Image
  • HTML 6 Dedicated Libraries
  • Annotations for Images and Videos
  • Authentication Enhancement
  • Customized Menus in HTML6
  • HTML6 Integrated Camera
  • Good Microformats
  • Single-Page Apps without JavaScript 
به زودی در HTML6