پاسخ به بازخورد‌های پروژه‌ها
خطا هنگام اتصال
در لاگ ارسالی عنوان شده‌است که:
   Failed to listen on prefix 'http://localhost:14799/' because it conflicts with an existing registration on the machine.
   HTTP could not register URL http://localhost:14799/. Another application has already registered this URL with HTTP.SYS.
یعنی پورتی که انتخاب کردید، هم اکنون در سیستم در حال استفاده‌است. یک پورت آزاد دیگر را انتخاب کنید.
دستور خط فرمان
 netsh http show servicestate
لیست این نوع پورت‌های مورد استفاده‌ی جهت http listener را نمایش می‌دهد.
اشتراک‌ها
دوره کار با Angular 16 توسط Web API و Entity Framework Core

Angular 16 CRUD with .NET 7 Web API using Entity Framework Core - Full Course

📑 Contents:
00:00:00 Video Introduction
00:00:40 Angular and ASP.NET Core Udemy Course Demo
00:03:07 Prerequisites
00:03:37 Setting Up Development Environment
00:15:37 Create ASP.NET Core Web API
00:20:07 Understanding Files and Folder Structure
00:25:37 Understanding REST and HTTP Verbs
00:30:10 Create .NET 6 Web API
00:32:41 Our Project and Domain Models
00:41:16 Installing Nuget Packages For Entity Framework Core
00:43:06 DbContext
00:59:26 Running EF Core Migrations
01:03:26 Create Controllers and Actions
01:23:46 Repository Pattern
01:36:46 Create New Angular Application using Angular CLI
01:50:09 Angular Components
02:13:29 CRUD in Angular and ASP.NET Core Web APIs
02:17:21 Angular Forms
02:26:59 Angular Services
02:38:09 CORS
02:42:09 Unsubscribing
 

دوره کار با Angular 16 توسط Web API و Entity Framework Core
مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #2 - تبدیل ویژگی‌ها به نگاشت
پس از معرفی ویژگی‌های لازم، در ادامه با نحوه‌ی تبدیل این ویژگی‌ها به معادل نگاشت آن‌ها در automapper خواهم پرداخت.
متد زیر هسته‌ی اصلی عملیات است و کلیه‌ی نگاشت‌های لازم را انجام می‌دهد. این متد وظیفه‌ی تبدیل نگاشت‌ها را دارد. نگاشت‌هایی که با Attributes مشخص شده‌اند:
 public static void Initialize(Assembly assembly)
 {
     //register global convertors.
     AutoMapper.Mapper.CreateMap<DateTime, string>().ConvertUsing<DateTimeToPersianDateTimeConverter>();

     var typesToMap = from t in assembly.GetTypes()
         let attr = t.GetCustomAttribute<MapFromAttribute>()
         where attr != null
         select new {SourceType = attr.SourceType, Destination = t, Attribute = attr};

     foreach (var map in typesToMap)
     {
         AutoMapper.Mapper.CreateMap(map.SourceType, map.Destination)             
             .DoMapForMemberAttribute() // for different property names in source and destination
             .DoIgnoreMapAttribute()// ignore specified properties
             .DoUseValueResolverAttribute()// set value resolvers
             .DoIgnoreAllNonExisting()// its have to be the latest.
             ;
     } //endeach
     AutoMapper.Mapper.AssertConfigurationIsValid();
 }
ورودی این متد اسملبی مربوط به ویوومدل می‌باشد (برای زمانیکه ویوومدل‌ها در اسمبلی دیگری باشند).
در سطر اول، اقدام به رجیستر کردن کلیه‌ی مبدل‌های سراسری می‌کنیم. در این سطر مبدل تاریخ به کوچی خورشیدی مورد استفاده قرار گرفته است. سپس در اسمبلی داده شده، کلیه نوع‌هایی که ویژگی MapFromAttribute را دارند، یافته و جدا می‌کنیم. در حلقه‌ی foreach ابتدا نگاشت نوع مبدأ و مقصد را انجام می‌دهیم. خروجی این متد از نوع IMappingExpression است. گر چه این اینترفیس برای تغییر بسته است، ولی قابل توسعه می‌باشد و عملیات را توسط متدهای الحاقی انجام می‌دهیم(اصل OCP).
اگر به نحوه‌ی نامگذاری متدهای الحاقی تعریف شده دقت کرده باشید، تنها کلمه‌ی Do به ابتدای نام ویژگی‌ها اضافه شده است
.

متد الحاقی DoMapFormMemberAttribute
public static IMappingExpression DoMapForMemberAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<MapForMemberAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

     foreach (var property in ok)
     {
         expression.ForMember(property.PropertyName, 
             opt => opt.MapFrom(property.AttributeValue.MemberToMap));
     }
    return expression;
}
هر IMappingExpression دارای امکاناتی برای نگهداری و انجام فعالیت بر روی یک نگاشت می‌باشد. در کوئری ابتدای متد، کلیه‌ی پروپرتی‌هایی را که دارای ویژگی MapForMemeberAttribute می‌باشند، یافته و جدا می‌کنیم. این پروپرتی‌ها از نظر معادل اسمی در نوع مبدأ و مقصد متفاوت هستند. سپس در حلقه، کار اتصال پروپرتی مبدأ و مقصد صورت می‌گیرد.

متد الحاقی DoIgnoreMapAttribute  
public static IMappingExpression DoIgnoreAttribute(this IMappingExpression expression)
{
    foreach (var property in
        expression.TypeMap.DestinationType.GetProperties()
        .Where(x => x.GetCustomAttribute<IgnoreMapAttribute>() != null))
    {
        expression.ForMember(property.Name, opt => opt.Ignore());
    }
    return expression;
}
این متد کلیه‌ی پروپرتی‌هایی را که دارای ویژگی IgnoreMapAttribute باشند، از گردونه‌ی نگاشت automapper خارج می‌کند. به عنوان مثال پروپرتی Password در ویوومدل مربوط به تغییر گذرواژه را نظر بگیرید. این پروپرتی نباید مقدار معادلی در شیء EF داشته باشد. از طرفی هم باید در ویوو وجودداشته باشد. با استفاده از این ویژگی هیچ نگاشتی انجام نمی‌شود و می‌توان تضمین کرد که گذرواژه به ویوومدل و ویوو راه پیدا نمی‌کند.

متد الحاقی DoUseValueResolverAttribute 
public static IMappingExpression DoUseValueResolverAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<UseValueResolverAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

    foreach (var property in ok)
    {
        expression.ForMember(property.PropertyName,
            opt => opt.ResolveUsing(property.AttributeValue.ValueResolver));
    }
    return expression;
}
به شیوه‌ی قبل، ابتدا نوع هایی را که دارای ویژگی UseValueResolverAttribute باشند، یافته و جدا می‌کنیم. سپس در حلقه، کار نگاشت متناظر در automapper انجام می‌گیرد. لازم به ذکر است که متد opt.ResolveUsing یک شیء با کارآیی (can do) اینترفیس IValueResolver را به عنوان آرگومان می‌گیرد.

متد الحاقی DoIgnoreAllNonExisting  
public static IMappingExpression DoIgnoreAllNonExisting(this IMappingExpression expression)
{
    var attr = expression.TypeMap.DestinationType.GetCustomAttribute<MapFromAttribute>();
    
    if (attr?.IgnoreAllNonExistingProperty == false)//instead of if(attr == null || attr.IgnoreAllNonExistingProperty == false)
        return expression;
    
    foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}
این متد برحسب پرچم تعیین شده در هنگام بکارگیری ویژگی MapFromAttribute رفتار می‌کند. به این صورت که اگر موقع تعریف، مقدار IgnoreAllNonExistingProperty را صحیح اعلام کنیم، تمام پروپرتی‌های مقصد را که معادل اسمی در مبدأ نداشته باشند و همچنین هیچگونه تنظیمی جهت مشخص سازی تکلیف نگاشت آن‌ها صورت نگرفته باشد، از گردونه‌ی نگاشت Automapper خارج می‌کند.

توضیح تکمیلی:
پس از تنظیم کلیه‌ی نگاشت‌ها در automapper جهت اطمینان از صحت تنظیمات، فراخوانی متد AutoMapper.Mapper.AssertConfigurationIsValid الزامی است. یکی از عواملی که باعث شکست این متد می‌شود، وجود پروپرتی‌هایی در نوع مقصد است، بطوریکه معادل اسمی در نوع مبدأ نداشته باشند و یا تنظیمی جهت مشخص سازی نگاشت آن انجام نشده باشد (پروپرتی که قابل نگاشت نباشد). در حقیقت این شکست بسیار مفید است. به این صورت که اگر این شکست صورت نگیرد در حین نگاشت مقادیر، باید از null یا مقدار default بدون اطلاع برنامه نویس برای مقداردهی پروپرتی استفاده کند و این یک حالت نامعلوم شیء است. اگر می‌خواهید این پروپرتی‌ها مقدار پیشفرضی بگیرند و همچنین باعث شکست عملیات هم نشوند، باید بطور صریح این موضوع را اعلام کنید. این اعلام یا باید به همین روش صورت بگیرد یا باید از ویژگی IgnorMapAttribute استفاده شود. تنها تفاوت این دو، نحوه‌ی اعمال تنظیم می‌باشد. IgnorMapAttribute باید روی تک تک پروپرتی‌های مدنظر قرار گیرد، ولی در روش اول تنها کافیست که مقدار true تنظیم گردد. به‌نظر استفاده از IgnoreMapAttribute باعث طولانی شدن کدها می‌شود؛ اما توصیه می‌شود که از همین شیوه استفاده کنید.

تا اینجا کدهای مورد نیاز نوشته شدند. در ادامه به ارائه‌ی یک مثال برای نگاشت اشیاء در Automapper توسط Attributeها می‌پردازم.
مدل ساده‌ی زیر را در نظر بگیرید:
public class Student
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual string Family { set; get; }
    public virtual string Email { set; get; }
    public virtual DateTime RegisterDateTime { set; get; }
    public virtual ICollection<Book> Books { set; get; }
}
public class Book
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual DateTime BorrowDateTime { set; get; }
    public virtual DateTime ExpiredDateTime { set; get; }
    public virtual decimal Price { set; get; }
    [ForeignKey("StudentIdFk")]
    public virtual Student Student { set; get; }
    public virtual int StudentIdFk { set; get; }
}
با ویوومدل متناظر ذیل:
[MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
public class AdminStudentViewModel
{
    // [IgnoreMap]
    public int Id { set; get; }

    [MapForMember("Name")]
    public string FirstName { set; get; }

    [MapForMember("Family")]
    public string LastName { set; get; }

 [IgnoreMap]  public string Email { set; get; } [MapForMember("RegisterDateTime")] public string RegisterDateTimePersian { set; get; } [UseValueResolver(typeof (BookCountValueResolver))] public int BookCounts { set; get; } [UseValueResolver(typeof (BookPriceValueResolver))] public decimal TotalBookPrice { set; get; } };
در تنظیم ویژگی MapFromAttribute ابتدا نوع مبدأ (Student) را مشخص کردیم و بعد صراحتاً گفتیم که از نگاشت پروپرتی‌های بلاتکلیف صرف نظر کند و همچنین پرچم انتقال Data Annotation‌های EF به ویوومدل را هم برافراشتیم. توسط MapForMember پروپرتی FirstName را به پروپرتی Name در مبدأ تنظیم کردیم و LastName را به Family. همچنین Email را بصورت صریح از نگاشت شدن منع کردیم. پروپرتی BookCounts تعداد کتاب‌ها را محاسبه می‌کند و TotalBookPrice قیمت کلیه‌ی کتاب‌ها را. برای این موارد از تأمین کننده‌ی داده (Value Resolver) استفاده کردیم. این تأمین کننده‌ها می‌توانند اینچنین پیاده سازی شوند:
public class BookCountValueResolver : ValueResolver<Student, int>
{
    protected override int ResolveCore(Student source) => source.Books.Count;
};
public class BookPriceValueResolver : ValueResolver<Student, decimal>
{
    protected override decimal ResolveCore(Student source) => source.Books.Sum(b => b.Price);
};
نحوه‌ی پیکربندی و مشاهده‌ی نتایج را در یک برنامه‌ی تحت کنسول پیاده سازی کردم. متد Main آن می‌تواند اینچنین باشد:
static void Main(string[] args)
{
    var assemblyToLoad = Assembly.GetAssembly(typeof (AdminStudentViewModel));//get assembly
    global::AttributesForAutomapper.Configuration.Initialize(assemblyToLoad);//init automaper
    IList<Student> lst;
    using (var context = new MySampleContext())
    {
        lst = context.Students.Include(x => x.Books).ToList();
    }
    foreach (var student in lst)
        {
            WriteLine( $"[{student.Id}]*\n{student.Name} {student.Family}.\nmailto:{student.Email}.\nRegistered at'{student.RegisterDateTime}'");
            foreach (var book in student.Books)
                WriteLine($"\tBook name:{book.Name}, Book price:{book.Price}");
        }
    
    var lstViewModel = AutoMapper.Mapper.Map<IList<Student>, IList<AdminStudentViewModel>>(lst);
    foreach (var adminStudentViewModel in lstViewModel)
    {
        WriteLine(
            $"[{adminStudentViewModel.Id}]*\n\t{adminStudentViewModel.FirstName} {adminStudentViewModel.LastName}.\n\t" +
            $"mailto:{adminStudentViewModel.Email}.\n\tRegistered at'{adminStudentViewModel.RegisterDateTimePersian}'\n\t" +
            $"Book Counts: {adminStudentViewModel.BookCounts} with total price of {adminStudentViewModel.TotalBookPrice}");
    }
    WriteLine("Press any key to exit...");
    ReadKey();
}
ابتدا اسمبلی مربوط به ویوومدل‌ها را مشخص می‌کنیم. سپس این اسمبلی را جهت تبدیل ویژگی‌ها به نگاشت‌های معتبر automapper به متد Initialize ارسال می‌کنیم. تنها بکار بردن همین دوسطر برای اعمال تنظیم‌ها مورد نیاز می‌باشد. بعد از اجرای موفق متد Initialize، نگاشت‌های اشیاء آماده هستند.
نمونه‌ی خروجی:
[1]*
Morteza Raeisi.
mailto:MrRaeisi@outlook.com.
Registered at'23/08/1392 19:11:43'    // I'm using Windows 10 with Persian calendar as default, On other OS or calendar settings, this value is different.
        Book name:AutoMapper Attr, Book price:1000.00
        Book name:Second Book, Book price:2500.00
        Book name:Hungry Book, Book price:2500.00
...
[1]*
Morteza Raeisi. //MapForMemebers
mailto:.  // IgnoreMap
Registered at'1392/08/23 19:11' // Convert using
Book Counts: 3 with total price of 6000.00  // Value resolvers
...
دریافت کدها + مثال
مطالب
تغییر اندازه تصاویر #1
برای برنامه نویسان همیشه این امکان هست که تصاویری را که از کاربر دریافت می‌کنند تغییر اندازه دهند، مثلا در همین سایت تصاویری از کاربران جهت نمایش در پروفایل آنها دریافت می‌شود، در همین سایت نیز این اتفاق می‌افتد مثلا تصاویر پروفایل کاربران با اندازه‌های متفاوتی نشان داده می‌شود.

برای انجام این کارها میتوان به دو طریق عمل کرد:
  1. تغییر اندازه تصویر در زمان ذخیره‌سازی
  2. در زمانی که می‌خواهیم تصویر را به بازدید کننده نشان دهیم
در حالت 1 زمانی که تصویری را از کاربر دریافت می‌کنیم با توجه به اینکه تصویر را با چه اندازه‌هایی در نرم افزار نیاز داریم تغییر اندازه داده و تک تک ذخیره می‌کنیم، این روش کل عملیات در زمان ثبت و تنها یکبار اتفاق می‌افتد، این روش جای بیشتری از منابع (مانند هارد دیسک یا دیتابیس) سرور را اشغال می‌کند اما در عوض می‌توان گفت سرعت بالاتری دارد، در روش دوم زمانی که بازدید کننده از سایت (نرم افزار) بازدید می‌کند تصویر اصلی با توجه به نیاز تغییر اندازه داده شده و برای کاربر ارسال می‌شود (در واقع کاربر آن را مشاهده می‌کند)، این روش فضای کمتری از منابع را اشغال می‌کند اما در زمان اجرا عملیات اضافی برای هر کاربری (البته با کش کردن این عملیات کم می‌شود) انجام می‌شود.
در هر دو روش گفته شده در هر صورت ما باید متد (توابعی) برای تغییر اندازه تصویر داشته باشیم که در زیر نحوه نوشتن آن را شرح خواهیم داد.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace PWS
{
    public static class Helpers
    {
        /// <summary>
        /// تغییر اندازه تصویر
        /// </summary>
        /// <param name="imageFile">آرایه بایتی از تصویر مورد نظر</param>
        /// <param name="targetSize">اندازه تصویر خروجی</param>
        /// <param name="format">فرمت تصویر خروجی</param>
        /// <returns></returns>
        public static byte[] ResizeImageFile(this byte[] imageFile, Int32 targetSize, ImageFormat format)
        {
            if (imageFile == null)
                throw new Exception("لطفا تصویر اصلی را مشخص نمایید");
            //باز کردن تصویر اصلی به عنوان یک جریان
            using (var oldImage = Image.FromStream(new MemoryStream(imageFile)))
            {
                //محاسبه اندازه تصویر خروجی با توجه به اندازه داده شده
                var newSize = CalculateDimensions(oldImage.Size, targetSize);
                //ایجاد تصویر جدید
                using (var newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format24bppRgb))
                {
                    using (var canvas = Graphics.FromImage(newImage))
                    {
                        canvas.SmoothingMode = SmoothingMode.AntiAlias;
                        canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
                        //تغییر اندازه تصویر اصلی و قرار دادن آن در تصویر جدید
                        canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
                        var m = new MemoryStream();
                        //ذخیره تصویر جدید با فرمت وارد شده
                        newImage.Save(m, format);
                        return m.GetBuffer();
                    }
                }
            }
        }

        /// <summary>
        /// محاسبه ابعاد تصویر خروجی
        /// </summary>
        /// <param name="oldSize">اندازه تصویر اصلی</param>
        /// <param name="targetSize">اندازه تصویر خروجی</param>
        /// <returns></returns>
        private static Size CalculateDimensions(Size oldSize, Int32 targetSize)
        {
            var newSize = new Size();
            if (oldSize.Height > oldSize.Width)
            {
                newSize.Width = Convert.ToInt32(oldSize.Width * (targetSize / (float)oldSize.Height));
                newSize.Height = targetSize;
            }
            else
            {
                newSize.Width = targetSize;
                newSize.Height = Convert.ToInt32(oldSize.Height * (targetSize / (float)oldSize.Width));
            }
            return newSize;
        }
    }
}

  1. در متد ResizeImageFileتصویر اصلی به عنوان یک جریان باز می‌شود. (سطر 23)
  2. اندازه تصویر خروجی با توجه به اندازه وارد شده توسط متد CalculateDimensions تعیین می‌شود؛ روال کار متد CalculateDimensions اینگونه است که اندازه عرض و ارتفاع تصویر اصلی بررسی می‌شود و با توجه به اینکه کدام یک از اینها بزرگتر است تغییر اندازه در آن صورت می‌گیرد، مثلا در صورتی که عکس ارتفاع بیشتری نسبت به عرض تصویر داشته باشد تصویر تغییر اندازه داده شده نیز با توجه به تناسب ارتفاع تغییر داده می‌شود و بالعکس. (سطر 26)
  3. پس از تغییر اندازه تصویر جدیدی در حافظه ایجاد می‌شود. (سطر 28)
  4. سپس تنظیمات گرافیکی لازم بروی تصویر جدید اعمال می‌شود. (سطر 30 تا 34)
  5. تصویر اصلی با توجه به اندازه جدید تغییر کرده و در تصویر جدید قرار می‌گیرد. (سطر 36)
  6. در نهایت تصویر جدید با فرمت وارد شده در متد ذخیره شده و به عنوان خروجی متد برگشت داده می‌شود. (سطر 37 تا 40)
خروجی این متد نیز آرایه بایتی می‌باشد که به سادگی می‌توانید از آن برای ذخیره تصاویر در دیتابیس استفاده نمایید.

نحوه استفاده از این تابع در ASP.NET می‌تواند به صورت زیر باشد :
byte[] oldImage = FileUploadImage.FileBytes;
byte[] target = oldImage.ResizeImageFile(100, ImageFormat.Jpeg);
در واقع فراخوانی مذکور تصویر ورودی را به اندازه 100 پیکسل تغییر داده و در ارایه بایتی به نام target ذخیره می‌کند.
در بخش بعد در زمان نمایش تصویر به کاربر آن را تغییر اندازه خواهیم داد.
لازم به ذکر است که کد‌های تغییر اندازه از StarterKit‌های میکروسافت کپی‌برداری شده است.
مطالب
بررسی مقدمات کتابخانه‌ی JSON.NET
چرا JSON.NET؟
JSON.NET یک کتابخانه‌ی سورس باز کار با اشیاء JSON در دات نت است. تاریخچه‌ی آن به 8 سال قبل بر می‌گردد و توسط یک برنامه نویس نیوزیلندی به نام James Newton King تهیه شده‌است. اولین نگارش آن در سال 2006 ارائه شد؛ مقارن با زمانی که اولین استاندارد JSON نیز ارائه گردید.
این کتابخانه از آن زمان تا کنون، 6 میلیون بار دانلود شده‌است و به علت کیفیت بالای آن، این روزها پایه اصلی بسیاری از کتابخانه‌ها و فریم ورک‌های دات نتی می‌باشد؛ مانند RavenDB تا ASP.NET Web API و SignalR مایکروسافت و همچنین گوگل نیز از آن جهت تدارک کلاینت‌های کار با API خود استفاده می‌کنند.
هرچند دات نت برای نمونه در نگارش سوم آن جهت مصارف WCF کلاسی را به نام DataContractJsonSerializer ارائه کرد، اما کار کردن با آن محدود است به فرمت خاص WCF به همراه عدم انعطاف پذیری و سادگی کار با آن. به علاوه باید درنظر داشت که JSON.NET از دات نت 2 به بعد تا مونو، Win8 و ویندوز فون را نیز پشتیبانی می‌کند.

برای نصب آن نیز کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنید:
 PM> install-package Newtonsoft.Json

معماری JSON.NET

کتابخانه‌ی JSON.NET از سه قسمت عمده تشکیل شده‌است:
الف) JsonSerializer
ب) LINQ to JSON
ج) JSON Schema


الف) JsonSerializer
کار JsonSerializer تبدیل اشیاء دات نتی به JSON و برعکس است. مزیت مهم آن امکانات قابل توجه تنظیم عملکرد و خروجی آن می‌باشد که این تنظیمات را به شکل ویژگی‌های خواص نیز می‌توان اعمال نمود. به علاوه امکان سفارشی سازی هر کدام نیز توسط کلاسی به نام JsonConverter، پیش بینی شده‌است.
یک مثال:
 var roles = new List<string>
{
   "Admin",
   "User"
};
string json = JsonConvert.SerializeObject(roles, Formatting.Indented);
در اینجا نحوه‌ی استفاده از JSON.NET را جهت تبدیل یک شیء دات نتی، به معادل JSON آن مشاهده می‌کنید. اعمال تنظیم Formatting.Indented سبب خواهد شد تا خروجی آن دارای Indentation باشد. برای نمونه اگر در برنامه‌ی خود قصد دارید فرمت JSON تو در تویی را به نحو زیبا و خوانایی نمایش دهید یا چاپ کنید، همین تنظیم ساده کافی خواهد بود.
و یا در مثال ذیل استفاده از یک anonymous object را مشاهده می‌کنید:
 var jsonString = JsonConvert.SerializeObject(new
{
   Id =1,
   Name = "Test"
}, Formatting.Indented);
به صورت پیش فرض تنها خواص عمومی کلاس‌ها توسط JSON.NET تبدیل خواهند شد.


تنظیمات پیشرفته‌تر JSON.NET

مزیت مهم JSON.NET بر سایر کتابخانه‌ها‌ی موجود مشابه، قابلیت‌های سفارشی سازی قابل توجه آن است. در مثال ذیل نحوه‌ی معرفی JsonSerializerSettings را مشاهده می‌نمائید:
var jsonData = JsonConvert.SerializeObject(new
{
   Id = 1,
   Name = "Test",
   DateTime = DateTime.Now
}, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   Converters =
   {
      new JavaScriptDateTimeConverter()
   }
});
در اینجا با استفاده از تنظیم JavaScriptDateTimeConverter، می‌توان خروجی DateTime استانداردی را به مصرف کنندگان جاوا اسکریپتی سمت کاربر ارائه داد؛ با خروجی ذیل:
 {
  "Id": 1,
  "Name": "Test",
  "DateTime": new Date(1409821985245)
}


نوشتن خروجی JSON در یک استریم

خروجی متد JsonConvert.SerializeObject یک رشته‌است که در صورت نیاز به سادگی توسط متد File.WriteAllText در یک فایل قابل ذخیره می‌باشد. اما برای رسیدن به حداکثر کارآیی و سرعت می‌توان از استریم‌ها نیز استفاده کرد:
using (var stream = File.CreateText(@"c:\output.json"))
{
    var jsonSerializer = new JsonSerializer
   {
      Formatting = Formatting.Indented
   };
   jsonSerializer.Serialize(stream, new
   {
     Id = 1,
     Name = "Test",
     DateTime = DateTime.Now
   });
}
کلاس JsonSerializer و متد Serialize آن یک استریم را نیز جهت نوشتن خروجی می‌پذیرند. برای مثال response.Output برنامه‌های وب نیز یک استریم است و در اینجا نوشتن مستقیم در استریم بسیار سریعتر است از تبدیل شیء به رشته و سپس ارائه خروجی آن؛ زیرا سربار تهیه رشته JSON از آن حذف می‌گردد و نهایتا GC کار کمتری را باید انجام دهد.


تبدیل JSON رشته‌ای به اشیاء دات نت

اگر رشته‌ی jsonData ایی را که پیشتر تولید کردیم، بخواهیم تبدیل به نمونه‌ای از شیء User ذیل کنیم:
public class User
{
   public int Id { set; get; }
   public string Name { set; get; }
   public DateTime DateTime { set; get; }
}
خواهیم داشت:
 var user = JsonConvert.DeserializeObject<User>(jsonData);
در اینجا از متد DeserializeObject به همراه مشخص سازی صریح نوع شیء نهایی استفاده شده‌است.
البته در اینجا با توجه به استفاده از JavaScriptDateTimeConverter برای تولید jsonData، نیاز است چنین تنظیمی را نیز در حالت DeserializeObject مشخص کنیم:
var user = JsonConvert.DeserializeObject<User>(jsonData, new JsonSerializerSettings
{
   Converters = {  new JavaScriptDateTimeConverter() }
});


مقدار دهی یک نمونه یا وهله‌ی از پیش موجود

متد JsonConvert.DeserializeObject یک شیء جدید را ایجاد می‌کند. اگر قصد دارید صرفا تعدادی از خواص یک وهله‌ی موجود، توسط JSON.NET مقدار دهی شوند از متد PopulateObject استفاده کنید:
 JsonConvert.PopulateObject(jsonData, user);


کاهش حجم JSON تولیدی

زمانیکه از متد JsonConvert.SerializeObject استفاده می‌کنیم، تمام خواص عمومی تبدیل به معادل JSON آن‌ها خواهند شد؛ حتی خواصی که مقدار ندارند. این خواص در خروجی JSON، با مقدار null مشخص می‌شوند. برای حذف این خواص از خروجی JSON نهایی تنها کافی است در تنظیمات JsonSerializerSettings، مقدار NullValueHandling = NullValueHandling.Ignore مشخص گردد.
var jsonData = JsonConvert.SerializeObject(object, new JsonSerializerSettings
{
   NullValueHandling = NullValueHandling.Ignore,
   Formatting = Formatting.Indented
});
مورد دیگری که سبب کاهش حجم خروجی نهایی خواهد شد، تنظیم DefaultValueHandling = DefaultValueHandling.Ignore است. در این حالت کلیه خواصی که دارای مقدار پیش فرض خودشان هستند، در خروجی JSON ظاهر نخواهند شد. مثلا مقدار پیش فرض خاصیت int مساوی صفر است. در این حالت کلیه خواص از نوع int که دارای مقدار صفر می‌باشند، در خروجی قرار نمی‌گیرند.
به علاوه حذف Formatting = Formatting.Indented نیز توصیه می‌گردد. در این حالت فشرده‌ترین خروجی ممکن حاصل خواهد شد.


مدیریت ارث بری توسط JSON.NET

در مثال ذیل کلاس کارمند و کلاس مدیر را که خود نیز در اصل یک کارمند می‌باشد، ملاحظه می‌کنید:
public class Employee
{
    public string Name { set; get; }
}

public class Manager : Employee
{
    public IList<Employee> Reports { set; get; }
}
در اینجا هر مدیر لیست کارمندانی را که به او گزارش می‌دهند نیز به همراه دارد. در ادامه نمونه‌ای از مقدار دهی این اشیاء ذکر شده‌اند:
 var employee = new Employee { Name = "User1" };
var manager1 = new Manager { Name = "User2" };
var manager2 = new Manager { Name = "User3" };
manager1.Reports = new[] { employee, manager2 };
manager2.Reports = new[] { employee };
با فراخوانی
 var list = JsonConvert.SerializeObject(manager1, Formatting.Indented);
یک چنین خروجی JSON ایی حاصل می‌شود:
{
  "Reports": [
    {
      "Name": "User1"
    },
    {
      "Reports": [
        {
          "Name": "User1"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
این خروجی JSON جهت تبدیل به نمونه‌ی معادل دات نتی خود، برای مثال جهت رسیدن به manager1 در کدهای فوق، چندین مشکل را به همراه دارد:
- در اینجا مشخص نیست که این اشیاء، کارمند هستند یا مدیر. برای مثال مشخص نیست User2 چه نوعی دارد و باید به کدام شیء نگاشت شود.
- مشکل دوم در مورد کاربر User1 است که در دو قسمت تکرار شده‌است. این شیء JSON اگر به نمونه‌ی معادل دات نتی خود نگاشت شود، به دو وهله از User1 خواهیم رسید و نه یک وهله‌ی اصلی که سبب تولید این خروجی JSON شده‌است.

برای حل این دو مشکل، تغییرات ذیل را می‌توان به JSON.NET اعمال کرد:
var list = JsonConvert.SerializeObject(manager1, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   TypeNameHandling = TypeNameHandling.Objects,
   PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
با این خروجی:
{
  "$id": "1",
  "$type": "JsonNetTests.Manager, JsonNetTests",
  "Reports": [
    {
      "$id": "2",
      "$type": "JsonNetTests.Employee, JsonNetTests",
      "Name": "User1"
    },
    {
      "$id": "3",
      "$type": "JsonNetTests.Manager, JsonNetTests",
      "Reports": [
        {
          "$ref": "2"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
- با تنظیم TypeNameHandling = TypeNameHandling.Objects سبب خواهیم شد تا خاصیت اضافه‌ای به نام $type به خروجی JSON اضافه شود. این نوع، در حین فراخوانی متد JsonConvert.DeserializeObject جهت تشخیص صحیح نگاشت اشیاء بکار گرفته خواهد شد و اینبار مشخص است که کدام شیء، کارمند است و کدامیک مدیر.
- با تنظیم PreserveReferencesHandling = PreserveReferencesHandling.Objects شماره Id خودکاری نیز به خروجی JSON اضافه می‌گردد. اینبار اگر به گزارش دهنده‌ها با دقت نگاه کنیم، مقدار $ref=2 را خواهیم دید. این مورد سبب می‌شود تا در حین نگاشت نهایی، دو وهله متفاوت از شیء با Id=2 تولید نشود.

باید دقت داشت که در حین استفاده از JsonConvert.DeserializeObject نیز باید JsonSerializerSettings یاد شده، تنظیم شوند.


ویژگی‌های قابل تنظیم در JSON.NET

علاوه بر JsonSerializerSettings که از آن صحبت شد، در JSON.NET امکان تنظیم یک سری از ویژگی‌ها به ازای خواص مختلف نیز وجود دارند.
- برای نمونه ویژگی JsonIgnore معروفترین آن‌ها است:
public class User
{
   public int Id { set; get; }

   [JsonIgnore]
   public string Name { set; get; }

   public DateTime DateTime { set; get; }
}
JsonIgnore سبب می‌شود تا خاصیتی در خروجی نهایی JSON تولیدی حضور نداشته باشد و از آن صرفنظر شود.

- با استفاده از ویژگی JsonProperty اغلب مواردی را که پیشتر بحث کردیم مانند NullValueHandling، TypeNameHandling و غیره، می‌توان تنظیم نمود. همچنین گاهی از اوقات کتابخانه‌های جاوا اسکریپتی سمت کاربر، از اسامی خاصی که از روش‌های نامگذاری دات نتی پیروی نمی‌کنند، در طراحی خود استفاده می‌کنند. در اینجا می‌توان نام خاصیت نهایی را که قرار است رندر شود نیز صریحا مشخص کرد. برای مثال:
[JsonProperty(PropertyName = "m_name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { set; get; }
همچنین در اینجا امکان تنظیم Order نیز وجود دارد. برای مثال مشخص کنیم که خاصیت X در ابتدا قرار گیرد و پس از آن خاصیت Y رندر شود.

- استفاده از ویژگی JsonObject به همراه مقدار OptIn آن به این معنا است که از کلیه خواصی که دارای ویژگی JsonProperty نیستند، صرفنظر شود. حالت پیش فرض آن OptOut است؛ یعنی تمام خواص عمومی در خروجی JSON حضور خواهند داشت منهای مواردی که با JsonIgnore مزین شوند.
[JsonObject(MemberSerialization.OptIn)]
public class User
{
    public int Id { set; get; }

    [JsonProperty]
    public string Name { set; get; }
 
    public DateTime DateTime { set; get; }
}

- با استفاده از ویژگی JsonConverter می‌توان نحوه‌ی رندر شدن مقدار خاصیت را سفارشی سازی کرد. برای مثال:
[JsonConverter(typeof(JavaScriptDateTimeConverter))]
public DateTime DateTime { set; get; }


تهیه یک JsonConverter سفارشی

با استفاده از JsonConverterها می‌توان کنترل کاملی را بر روی اعمال serialization و deserialization مقادیر خواص اعمال کرد. مثال زیر را در نظر بگیرید:
public class HtmlColor
{
   public int Red { set; get; }
   public int Green { set; get; }
   public int Blue { set; get; }
}

var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
}, Formatting.Indented);
در اینجا علاقمندیم، در حین عملیات serialization، بجای اینکه مقادیر اجزای رنگ تهیه شده به صورت int نمایش داده شوند، کل رنگ با فرمت hex رندر شوند. برای اینکار نیاز است یک JsonConverter سفارشی را تدارک دید:
    public class HtmlColorConverter : JsonConverter
    {

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(HtmlColor);
        }

        public override object ReadJson(JsonReader reader, Type objectType,
                                        object existingValue, JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var color = value as HtmlColor;
            if (color == null)
                return;

            writer.WriteValue("#" + color.Red.ToString("X2")
                + color.Green.ToString("X2") + color.Blue.ToString("X2"));
        }
    }
کار با ارث بری از کلاس پایه JsonConverter شروع می‌شود. سپس باید تعدادی از متدهای این کلاس پایه را بازنویسی کرد. در متد CanConvert اعلام می‌کنیم که تنها اشیایی از نوع کلاس HtmlColor را قرار است پردازش کنیم. سپس در متد WriteJson منطق سفارشی خود را می‌توان پیاده سازی کرد.
از آنجائیکه این تبدیلگر صرفا قرار است برای حالت serialization استفاده شود، قسمت ReadJson آن پیاده سازی نشده‌است.

در آخر برای استفاده از آن خواهیم داشت:
var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
},  new JsonSerializerSettings
    {
      Formatting = Formatting.Indented,
      Converters = { new HtmlColorConverter() }
    });   
نظرات مطالب
طراحی و پیاده سازی زیرساختی برای مدیریت خطاهای حاصل از Business Rule Validationها در ServiceLayer
بسیار ممنون از شما بابت این مقاله
چند پیشنهاد داشتم :
1 - در مورد  Railway-oriented Programming   مقاله جداگانه تهیه شود.
2- برای تکمیل این بخش مطلبی در مورد  Either   تهیه شود. برای اطلاع بیشتر
3- استفاده از لایه Service باعث نقض قانون  encapsulation شده و بهتر است در برنامه‌های بزرگ از آن به این طریق استفاده نشود زیرا باعث complex و discovery کد و درنهایت بروز خطا در برنامه خواهد شد. مانند:
namespace EF_Sample07.DomainClasses
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}
namespace EF_Sample07.ServiceLayer
{
    public interface IProductService
    {
        void AddNewProduct(Product product);
    }
}
برای حل این مشکل باید از immutable ، value object  و  Shadow Properties کمک  گرفت
namespace EF_Sample07.DomainClasses
{
    public class Product : Entity
    {
        public Product ( ProductName name, decimal price)
        {
              this.Name=name;
              this.Price = price;
         }  
        private string _name ;
        public ProductName Name { get=> (ProductName)_name;  set=> _ name = value } //value object with Shadow Properties  
        public decimal Price { get;}
        
    }
}

و شاید به دلیل یک سری از دلایل بخواهیم در مدل برنامه قانون encapsulation رو نقض کنیم که اگر با این سناریو مواجه شدیم میتوانیم از مهندس ساخت اشیا یعنی Builder استفاده ببریم.
4- بد نیست جامعه dotnettips این سری از مقاله ها رو به فارسی برگردونه.
 
مطالب
C# 6 - String Interpolation
تا پیش از C# 6 یکی از روش‌های توصیه شده‌ی جهت اتصال رشته‌ها به هم، استفاده از متدهایی مانند string.Format و StringBuilder.AppendFormat بود:
using System;
 
namespace CS6NewFeatures
{
    class Person
    {
        public string FirstName { set; get; }
        public string LastName { set; get; }
        public int Age { set; get; }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 };
            var message = string.Format("Hello!  My name is {0} {1} and I am {2} years old.",
                                          person.FirstName, person.LastName, person.Age);
            Console.Write(message);
        }
    }
}
مشکل این روش، کاهش خوانایی آن با بالا رفتن تعداد پارامترهای متد Format است و همچنین گاهی از اوقات فراموش کردن مقدار دهی بعضی از آن‌ها و یا حتی ذکر ایندکس‌هایی غیر معتبر که در زمان اجرا، برنامه را با یک خطا متوقف می‌کنند.
در C# 6 جهت رفع این مشکلات، راه حلی به نام String interpolation ارائه شده‌است و اگر افزونه‌ی ReSharper یا یکی از افزونه‌های Roslyn را نصب کرده باشید، به سادگی امکان تبدیل کدهای قدیمی را به فرمت جدید آن خواهید یافت:


در این حالت کد قدیمی فوق، به کد ذیل تبدیل خواهد شد:
static void Main(string[] args)
{
    var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 };
    var message = $"Hello!  My name is {person.FirstName} {person.LastName} and I am {person.Age} years old.";
    Console.Write(message);
}
در اینجا ابتدا یک $ در ابتدای رشته قرار گرفته و سپس هر متغیر به داخل {} انتقال یافته‌است. همچنین دیگر نیازی هم به ذکر string.Format نیست.
عملیاتی که در اینجا توسط کامپایلر صورت خواهد گرفت، تبدیل این کدهای جدید مبتنی بر String interpolation به همان string.Format قدیمی در پشت صحنه‌است. بنابراین این قابلیت جدید C# 6 را به کدهای قدیمی خود نیز می‌توانید اعمال کنید. فقط کافی است VS 2015 را نصب کرده باشید و دیگر شماره‌ی دات نت فریم ورک مورد استفاده مهم نیست.


امکان انجام محاسبات با String interpolation

زمانیکه $ در ابتدای رشته قرار گرفت، عبارات داخل {}‌ها توسط کامپایلر محاسبه و جایگزین می‌شوند. بنابراین می‌توان چنین محاسباتی را نیز انجام داد:
 var message2 = $"{Environment.NewLine}Test {DateTime.Now}, {3*2}";
Console.Write(message2);
بدیهی اگر $ ابتدای رشته فراموش شود، اتفاق خاصی رخ نخواهد داد.


تغییر فرمت عبارات نمایش داده شده توسط String interpolation

همانطور که با string.Format می‌توان نمایش سه رقم جدا کننده‌ی هزارها را فعال کرد و یا تاریخی را به نحوی خاص نمایش داد، در اینجا نیز همان قابلیت‌ها برقرار هستند و باید پس از ذکر یک : عنوان شوند:
 var message3 = $"{Environment.NewLine}{1000000:n0} {DateTime.Now:dd-MM-yyyy}";
Console.Write(message3);
حالت کلی و استاندارد آن در متد string.Format به صورت {index[,alignment][:formatString]} است.


سفارشی سازی String interpolation
 
اگر متغیر رشته‌‌ای معرفی شده‌ی توسط $ را با یک var مشخص کنیم، نوع آن به صورت پیش فرض، از نوع string خواهد بود. برای نمونه در مثال‌های فوق، message و message2 از نوع string تعریف می‌شوند. اما این رشته‌های ویژه را می‌توان از نوع IFormattable و یا FormattableString نیز تعریف کرد.
در حقیقت رشته‌های آغاز شده‌ی با $ از نوع IFormattable هستند و اگر نوع متغیر آن‌ها ذکر نشود، به صورت خودکار به نوع FormattableString که اینترفیس IFormattable را پیاده سازی می‌کند، تبدیل می‌شوند. بنابراین پیاده سازی این اینترفیس، امکان سفارشی سازی خروجی string interpolation را میسر می‌کند. برای نمونه می‌خواهیم در مثال message2، نحوه‌ی نمایش تاریخ را سفارشی سازی کنیم.
class MyDateFormatProvider : IFormatProvider
{
    readonly MyDateFormatter _formatter = new MyDateFormatter();
 
    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter) ? _formatter : null;
    }
 
    class MyDateFormatter : ICustomFormatter
    {
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg is DateTime)
                return ((DateTime)arg).ToString("MM/dd/yyyy");
            return arg.ToString();
        }
    }
}
در اینجا ابتدا کار با پیاده سازی اینترفیس IFormatProvider شروع می‌شود. متد GetFormat آن همیشه به همین شکل خواهد بود و هر زمانیکه نوع ارسالی به آن ICustomFormatter بود، یعنی یکی از اجزای {} دار در حال آنالیز است و خروجی مدنظر آن همیشه از نوع ICustomFormatter است که نمونه‌ای از پیاده سازی آن‌را جهت سفارشی سازی DateTime ملاحظه می‌کنید.
پس از پیاده سازی این سفارشی کننده‌ی تاریخ، نحوه‌ی استفاده‌ی از آن به صورت ذیل است:
static string formatMyDate(FormattableString formattable)
{
      return formattable.ToString(new MyDateFormatProvider());
}
ابتدا یک متد static را تعریف کنید که ورودی آن از نوع FormattableString باشد؛ از این جهت که رشته‌های شروع شده‌ی با $ نیز از همین نوع هستند. سپس سفارشی سازی پردازش {}‌ها در قسمت ToString آن انجام می‌شود و در اینجا می‌توان یک IFormatProvider جدید را معرفی کرد.
در ادامه برای اعمال این سفارشی سازی، فقط کافی است متد formatMyDate را به رشته‌ی مدنظر اعمال کنیم:
 var message2 = formatMyDate($"{Environment.NewLine}Test {DateTime.Now}, {3*2}");
Console.Write(message2);

و اگر تنها می‌خواهید فرهنگ جاری را عوض کنید، از روش ساده‌ی زیر استفاده نمائید:
public static string faIr(IFormattable formattable)
{
    return formattable.ToString(null, new CultureInfo("fa-Ir"));
}
در اینجا با اعمال متد faIr به عبارت شروع شده‌ی با $، فرهنگ ایران به رشته‌ی جاری اعمال خواهد شد.
نمونه‌ی کاربردی‌تر آن اعمال InvariantCulture به String interpolation است:
 static string invariant(FormattableString formattable)
{
    return formattable.ToString(CultureInfo.InvariantCulture);
}


یک نکته: همانطور که عنوان شد این قابلیت جدید با نگارش‌های قبلی دات نت نیز سازگار است؛ اما این کلاس‌های جدید را در این نگارش‌ها نخواهید یافت. برای رفع این مشکل تنها کافی است این کلاس‌های یاد شده را به صورت دستی در فضای نام اصلی آن‌ها تعریف و اضافه کنید. یک مثال


غیرفعال سازی String interpolation

اگر می‌خواهید در رشته‌ای که با $ شروع شده، بجای محاسبه‌ی عبارتی، دقیقا خود آن‌را نمایش دهید (و { را escape کنید)، از {{}} استفاده کنید:
 var message0 = $"Hello! My name is {person.FirstName} {{person.FirstName}}";
در این مثال اولین {} محاسبه خواهد شد و دومی خیر.


پردازش عبارات شرطی توسط String interpolation

همانطور که عنوان شد، امکان ذکر یک عبارت کامل هم در بین {} وجود دارد (محاسبات، ذکر یک عبارت LINQ، ذکر یک متد و امثال آن). اما در این میان اگر یک عبارت شرطی مدنظر بود، باید بین () قرار گیرد:
 Console.Write($"{(person.Age>50 ? "old": "young")}");
علت اینجا است که کامپایلر سی‌شارپ، : بین {} را به format specifier تفسیر می‌کند. نمونه‌ی آن‌را پیشتر با مثال «تغییر فرمت عبارات نمایش داده شده» ملاحظه کردید. ذکر : در اینجا به معنای شروع مشخص سازی فرمتی است که قرار است به این حاصل اعمال شود. برای تغییر این رفتار پیش فرض، کافی است عبارت مدنظر را بین () ذکر کنیم تا تمام آن به صورت یک عبارت سی‌شارپ تفسیر شود.
مطالب
حذف فضاهای خالی در خروجی صفحات ASP.NET MVC
صفحات خروجی وب سایت زمانی که رندر شده و در مرورگر نشان داده می‌شود شامل فواصل اضافی است که تاثیری در نمایش سایت نداشته و صرفا این کاراکترها فضای اضافی اشغال می‌کنند. با حذف این کاراکترهای اضافی می‌توان تا حد زیادی صفحه را کم حجم کرد. برای این کار در ASP.NET Webform کارهایی (^ ) انجام شده است.
روال کار به این صورت بوده که قبل از رندر شدن صفحه در سمت سرور خروجی نهایی بررسی شده و با استفاده از عبارات با قاعده الگوهای مورد نظر لیست شده و سپس حذف می‌شوند و در نهایت خروجی مورد نظر حاصل خواهد شد. برای راحتی کار و عدم نوشتن این روال در تمامی صفحات می‌تواند در مستر پیج این عمل را انجام داد. مثلا:
private static readonly Regex RegexBetweenTags = new Regex(@">\s+<", RegexOptions.Compiled);
        private static readonly Regex RegexLineBreaks = new Regex(@"\r\s+", RegexOptions.Compiled);

        protected override void Render(HtmlTextWriter writer)
        {
            using (var htmlwriter = new HtmlTextWriter(new System.IO.StringWriter()))
            {
                base.Render(htmlwriter);
                var html = htmlwriter.InnerWriter.ToString();

                html = RegexBetweenTags.Replace(html, "> <");
                html = RegexLineBreaks.Replace(html, string.Empty);
                html = html.Replace("//<![CDATA[", "").Replace("//]]>", "");
                html = html.Replace("// <![CDATA[", "").Replace("// ]]>", "");

                writer.Write(html.Trim());
            }
        }
در هر صفحه رویدادی به نام Render وجود دارد که خروجی نهایی را می‌توان در آن تغییر داد. همانگونه که مشاهده می‌شود عملیات یافتن و حذف فضاهای خالی در این متد انجام می‌شود.
این عمل در ASP.NET Webform به آسانی انجام شده و باعث حذف فضاهای خالی در خروجی صفحه می‌شود.
برای انجام این عمل در ASP.NET MVC روال کار به این صورت نیست و نمی‌توان مانند ASP.NET Webform عمل کرد.
چون در MVC از ViewPage استفاده می‌شود و ما مستقیما به خروجی آن دسترسی نداریم یک روش این است که می‌توانیم یک کلاس برای ViewPage تعریف کرده و رویداد Write آن را تحریف کرده و مانند مثال بالا فضای خالی را در خروجی حذف کرد. البته برای استفاده باید کلاس ایجاد شده را به عنوان فایل پایه جهت ایجاد صفحات در MVC فایل web.config معرفی کنیم. این روش در اینجا به وضوح شرح داده شده است.
اما هدف ما پیاده سازی با استفاده از اکشن فیلتر هاست. برای پیاده سازی ایتدا یک اکشن فیلتر به نام CompressAttribute تعریف می‌کنیم مانند زیر:
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;

namespace PWS.Common.ActionFilters
{
    public class CompressAttribute : ActionFilterAttribute
    {
         #region Methods (2) 

        // Public Methods (1) 

        /// <summary>
        /// Called by the ASP.NET MVC framework before the action method executes.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var response = filterContext.HttpContext.Response;
            if (IsGZipSupported(filterContext.HttpContext.Request))
            {
                String acceptEncoding = filterContext.HttpContext.Request.Headers["Accept-Encoding"];
                if (acceptEncoding.Contains("gzip"))
                {
                    response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "gzip");
                }
                else
                {
                    response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "deflate");
                }
            }
            // Allow proxy servers to cache encoded and unencoded versions separately
            response.AppendHeader("Vary", "Content-Encoding");
           //حذف فضاهای خالی
response.Filter = new WhitespaceFilter(response.Filter); } // Private Methods (1)  /// <summary> /// Determines whether [is G zip supported] [the specified request]. /// </summary> /// <param name="request">The request.</param> /// <returns></returns> private Boolean IsGZipSupported(HttpRequestBase request) { String acceptEncoding = request.Headers["Accept-Encoding"]; if (acceptEncoding == null) return false; return !String.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"); } #endregion Methods  } /// <summary> /// Whitespace Filter /// </summary> public class WhitespaceFilter : Stream { #region Fields (3)  private readonly Stream _filter; /// <summary> /// /// </summary> private static readonly Regex RegexAll = new Regex(@"\s+|\t\s+|\n\s+|\r\s+", RegexOptions.Compiled); /// <summary> /// /// </summary> private static readonly Regex RegexTags = new Regex(@">\s+<", RegexOptions.Compiled); #endregion Fields  #region Constructors (1)  /// <summary> /// Initializes a new instance of the <see cref="WhitespaceFilter" /> class. /// </summary> /// <param name="filter">The filter.</param> public WhitespaceFilter(Stream filter) { _filter = filter; } #endregion Constructors  #region Properties (5)  //methods that need to be overridden from stream /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports reading. /// </summary> /// <returns>true if the stream supports reading; otherwise, false.</returns> public override bool CanRead { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports seeking. /// </summary> /// <returns>true if the stream supports seeking; otherwise, false.</returns> public override bool CanSeek { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports writing. /// </summary> /// <returns>true if the stream supports writing; otherwise, false.</returns> public override bool CanWrite { get { return true; } } /// <summary> /// When overridden in a derived class, gets the length in bytes of the stream. /// </summary> /// <returns>A long value representing the length of the stream in bytes.</returns> public override long Length { get { return 0; } } /// <summary> /// When overridden in a derived class, gets or sets the position within the current stream. /// </summary> /// <returns>The current position within the stream.</returns> public override long Position { get; set; } #endregion Properties  #region Methods (6)  // Public Methods (6)  /// <summary> /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed. /// </summary> public override void Close() { _filter.Close(); } /// <summary> /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// </summary> public override void Flush() { _filter.Flush(); } /// <summary> /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// </summary> /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns> /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. /// </returns> public override int Read(byte[] buffer, int offset, int count) { return _filter.Read(buffer, offset, count); } /// <summary> /// When overridden in a derived class, sets the position within the current stream. /// </summary> /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param> /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param> /// <returns> /// The new position within the current stream. /// </returns> public override long Seek(long offset, SeekOrigin origin) { return _filter.Seek(offset, origin); } /// <summary> /// When overridden in a derived class, sets the length of the current stream. /// </summary> /// <param name="value">The desired length of the current stream in bytes.</param> public override void SetLength(long value) { _filter.SetLength(value); } /// <summary> /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. /// </summary> /// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param> /// <param name="count">The number of bytes to be written to the current stream.</param> public override void Write(byte[] buffer, int offset, int count) { string html = Encoding.Default.GetString(buffer); //remove whitespace html = RegexTags.Replace(html, "> <"); html = RegexAll.Replace(html, " "); byte[] outdata = Encoding.Default.GetBytes(html); //write bytes to stream _filter.Write(outdata, 0, outdata.GetLength(0)); } #endregion Methods  } }
در این کلاس فشرده سازی (gzip و deflate نیز اعمال شده است) در متد OnActionExecuting ابتدا در خط 24 بررسی می‌شود که آیا درخواست رسیده gzip را پشتیبانی می‌کند یا خیر. در صورت پشتیبانی خروجی صفحه را با استفاده از gzip یا deflate فشرده سازی می‌کند. تا اینجای کار ممکن است مورد نیاز ما نباشد. اصل کار ما (حذف کردن فضاهای خالی) در خط 42 اعمال شده است. در واقع برای حذف فضاهای خالی باید یک کلاس که از Stream ارث بری دارد تعریف شده و خروجی کلاس مورد نظر به فیلتر درخواست ما اعمال شود.
در کلاس WhitespaceFilter با تحریف متد Write الگوهای فضای خالی موجود در درخواست یافت شده و آنها را حذف می‌کنیم. در نهایت خروجی این کلاس که از نوع استریم است به ویژگی فیلتر صفحه اعمال می‌شود.

برای معرفی فیلتر تعریف شده می‌توان در فایل Global.asax در رویداد Application_Start به صورت زیر فیلتر مورد نظر را به فیلترهای MVC اعمال کرد.
GlobalFilters.Filters.Add(new CompressAttribute());
برای آشنایی بیشتر فیلترها در ASP.NET MVC را مطالعه نمایید.
پ.ن: جهت سهولت، در این کلاس ها، صفحات فشرده سازی و همزمان فضاهای خالی آنها حذف شده است.
مطالب دوره‌ها
متدهای async تقلبی
تا اینجا مشاهده کردیم که اگر یک چنین متد زمانبری را داشته باشیم که در آن عملیاتی طولانی انجام می‌شود،
class MyService
{
  public int CalculateXYZ()
  {
    // Tons of work to do in here!
    for (int i = 0; i != 10000000; ++i)
      ;
    return 42;
  }
}
برای نوشتن معادل async آن فقط کافی است که امضای متد را به async Task تغییر دهیم و سپس داخل آن از Task.Run استفاده کنیم:
class MyService
{
  public async Task<int> CalculateXYZAsync()
  {
    return await Task.Run(() =>
    {
      // Tons of work to do in here!
      for (int i = 0; i != 10000000; ++i)
        ;
      return 42;
    });
  }
}
و ... اگر از آن در یک کد UI استفاده کنیم، ترد آن‌را قفل نکرده و برنامه، پاسخگوی سایر درخواست‌های رسیده خواهد بود. اما ... به این روش اصطلاحا Fake Async گفته می‌شود؛ یا Async تقلبی!
کاری که در اینجا انجام شده، استفاده‌ی ناصحیح از Task.Run در حین طراحی یک متد و یک API است. عملیات انجام شده در آن واقعا غیرهمزمان نیست و در زمان انجام آن، باز هم ترد جدید اختصاص داده شده را تا پایان عملیات قفل می‌کند. اینجا است که باید بین CPU-bound operations و IO-bound operations تفاوت قائل شد. اگر Entity Framework 6 و یا کلاس WebClient و امثال آن، متدهایی Async را نیز ارائه داده‌اند، این‌ها به معنای واقعی کلمه، غیرهمزمان هستند و در آن‌ها کوچکترین CPU-bound operation ایی انجام نمی‌شود.
در حلقه‌ای که در مثال فوق در حال پردازش است و یا تمام اعمال انجام شده توسط CPU، از مرزهای سیستم عبور نمی‌کنیم. نه قرار است فایلی را ذخیره کنیم، نه با اینترنت سر و کار داشته باشیم و یا مثلا اطلاعاتی را از وب سرویسی دریافت کنیم و نه هیچگونه IO-bound operation خاصی قرار است صورت گیرد.
زمانیکه برنامه نویسی قرار است با API شما کار کند و به امضای async Task می‌رسد، فرضش بر این است که در این متد واقعا یک کار غیرهمزمان در حال انجام است. بنابراین جهت بالابردن کارآیی برنامه، این نسخه را نسبت به نمونه‌ی غیرهمزمان انتخاب می‌کند.
حال تصور کنید که استفاده کننده از این API یک برنامه‌ی دسکتاپ نیست، بلکه یک برنامه‌ی ASP.NET است. در اینجا Task.Run فراخوانی شده صرفا سبب خواهد شد عملیات مدنظر، بر روی یک ترد دیگر، نسبت به ترد اصلی اختصاص داده شده توسط ASP.NET برای فراخوانی و پردازش CalculateXYZAsync، صورت گیرد. این عملیات بهینه نیست. تمام پردازش‌های درخواست‌های ASP.NET در تردهای خاص خود انجام می‌شوند. وجود ترد دوم ایجاد شده توسط Task.Run در اینجا چه حاصلی را بجز سوئیچ بی‌جهت بین تردها و همچنین بالا بردن میزان کار Garbage collector دارد؟ در این حالت نه تنها سبب بالا بردن مقیاس پذیری سیستم نشده‌ایم، بلکه میزان کار Garbage collector و همچنین سوئیچ بین تردهای مختلف را در Thread pool برنامه به شدت افزایش داده‌ایم. همچنین یک چنین سیستمی برای تدارک تردهای بیشتر و مدیریت آن‌ها، مصرف حافظه‌ی بیشتری نیز خواهد داشت.


یک اصل مهم در طراحی کدهای Async
استفاده از Task.Run در پیاده سازی بدنه متدهای غیرهمزمان، یک code smell محسوب می‌شود.


چکار باید کرد؟
اگر در کدهای خود اعمال Async واقعی دارید که IO-bound هستند، از معادل‌های Async طراحی شده برای کار با آن‌ها، مانند متد SaveChangesAsync در EF، متد DownloadStringTaskAsync کلاس WebClient و یا متدهای جدید Async کلاس Stream برای خواندن و نوشتن اطلاعات استفاده کنید. در یک چنین حالتی ارائه متدهای async Task بسیار مفید بوده و در جهت بالابردن مقیاس پذیری سیستم بسیار مؤثر واقع خواهند شد.
اما اگر کدهای شما صرفا قرار است بر روی CPU اجرا شوند و تنها محاسباتی هستند، اجازه دهید مصرف کننده تصمیم بگیرد که آیا لازم است از Task.Run برای فراخوانی متد ارائه شده در کدهای خود استفاده کند یا خیر. اگر برنامه‌ی دسکتاپ است، این فراخوانی مفید بوده و سبب آزاد شدن ترد UI می‌شود. اگر برنامه‌ی وب است، به هیچ عنوان نیازی به Task.Run نبوده و فراخوانی متداول آن با توجه به اینکه درخواست‌های برنامه‌های ASP.NET در تردهای مجزایی اجرا می‌شوند، کفایت می‌کند.

به صورت خلاصه
از Task.Run در پیاده سازی بدنه متدهای API خود استفاده نکنید.
از Task.Run در صورت نیاز (مثلا در برنامه‌های دسکتاپ) در حین فراخوانی و استفاده از متدهای API ارائه شده استفاده نمائید:
 private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => myService.CalculateXYZ());
}
در این مثال از همان نسخه‌ی غیرهمزمان متد محاسباتی استفاده شده‌است و اینبار مصرف کننده است که تصمیم گرفته در حین فراخوانی و استفاده نهایی، برای آزاد سازی ترد UI از await Task.Run استفاده کند (یا خیر).

بنابراین نوشتن یک چنین کدهایی در پیاده سازی یک API غیرهمزمان
await Task.Run(() =>
{
   for (int i = 0; i != 10000000; ++i)
     ;
});
صرفا خود را گول زدن است. کل این عملیات بر روی CPU انجام شده و هیچگاه از مرزهای IO سیستم عبور نمی‌کند.

برای مطالعه بیشتر
Should I expose asynchronous wrappers for synchronous methods
نظرات مطالب
EF Code First #1
سلام
وقتی کانکش استرینگو به این صورت تعربف می‌کنم :
<configuration>
<configSections>
</configSections>
<connectionStrings>
<clear/>
<add name="Context" connectionString="Data Source=localhost;Initial Catalog=test;Integrated Security = true" providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.web>
<compilation debug="true"/></system.web>
</configuration>

این erorr میده :

An error occurred while getting provider information from the database. This can be caused by Entity Framework using an incorrect connection string. Check the inner exceptions for details and ensure that the connection string is correct. 
علتش چی می‌تونه باشه ؟