نظرات مطالب
ساخت ربات تلگرامی با #C
1. آپدیتهای دریافتی همیشه فقط شامل Message نمیشه و ممکنه آپدیت دریافتی از نوع CallbackQuery هم باشه
2. کمی بالاتر توضیح داده شده که برای دریافت آپدیت‌های جدید باید پارامتر offset رو هم ارسال کنی. مقدار این پارامتر باید رقم بعدی Id آخرین آپدیت دریافتی باشه یعنی update_id  +1
برای طراحی ربات تلگرام هم بهتر هست که از پکیج‌های آماده استفاده بشه که بالاتر عرض کردم.
یه پروژه کنسول ایجاد کن پکیج telegram.bot رو هم از Nuget به برنامه اضافه کن و کلاس program.cs  رو به صورت زیر پیاده کن
به جای BOT_TOKEN هم توکن ربات خودت رو کپی کن و برنامه رو اجرا کن
using System.Threading.Tasks;
using Telegram.Bot;

namespace Bot.Engine.Console
{
    public class Program
    {
        Api bot;
        string botToken = "BOT_TOKEN";

        public static void Main(string[] args)
        {
            Task.Run(() => RunBot(botToken));

            System.Console.ReadLine();
        }


        /// <summary>
        /// 
        /// </summary>
        public static async Task RunBot(string botToken)
        {
            #region راه اندازی ربات

             bot = new Api(botToken);
            var me = await bot.GetMe();
            if (me != null)
            {
                System.Console.WriteLine("bot started {0}", me.Username);
            }
            else
            {
                System.Console.WriteLine("get bot failed ");
            }

          
            #endregion

            #region شروع گوش دادن به درخواست‌ها var whileCount = 0;
            var offset = 0;

            while (true)
            {
                System.Console.WriteLine("while no {0}", whileCount);

                whileCount += 1;
                try
                {
                    var updates = await bot.GetUpdatesAsync(offset);
                    var updatesCount = updates.Count();
                    System.Console.WriteLine("updates count is {0}", updatesCount);
                    System.Console.WriteLine("================================================================");

                    if (updatesCount > 0)
                    {
                        foreach (var update in updates)
                        {
                            try
                            {
                                offset = update.Id + 1;
                                if (update.Message.Text!=null)
                                {
                                    //echo msg
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, update.Message.Text);
                                }
                                else
                                {
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, "لطفا یک پیام متنی بفرستید");
                                }

                            }
                            catch (Exception ex)
                            {
                                bot.SendTextMessage(update.Message.Chat.Id, ex.ToString());
                            }
                        }
                        continue;
                    }


                }
                catch (Exception ex)
                {
                    System.Console.WriteLine("Error Msg = {0}",ex.Message);
                }

            }

            #endregion
        }

    }
}

مطالب
آشنایی با Refactoring - قسمت 11

قسمت یازدهم آشنایی با Refactoring به توصیه‌هایی جهت بالا بردن خوانایی تعاریف مرتبط با اعمال شرطی می‌پردازد.

الف) شرط‌های ترکیبی را کپسوله کنید

عموما حین تعریف شرط‌های ترکیبی، هدف اصلی از تعریف آن‌ها پشت انبوهی از && و || گم می‌شود و برای بیان مقصود، نیاز به نوشتن کامنت خواهند داشت. مانند:

using System;

namespace Refactoring.Day11.EncapsulateConditional.Before
{
public class Element
{
private string[] Data { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }

public string FindElement()
{
if (Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1)
return "Element1";

if (Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2)
return "Element2";

return string.Empty;
}
}
}

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

using System;

namespace Refactoring.Day11.EncapsulateConditional.After
{
public class Element
{
private string[] Data { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }

public string FindElement()
{
if (hasOneYearOldElement)
return "Element1";

if (hasTwoYearsOldElement)
return "Element2";

return string.Empty;
}

private bool hasTwoYearsOldElement
{
get { return Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2; }
}

private bool hasOneYearOldElement
{
get { return Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1; }
}
}
}


همانطور که ملاحظه می‌کنید پس از این جایگزینی، خوانایی متد FindElement بهبود یافته است و برنامه نویس اگر 6 ماه بعد به این کدها مراجعه کند نخواهد گفت: «من این کدها رو نوشتم؟!»؛ چه برسد به سایرینی که احتمالا قرار است با این کدها کار کرده و یا آن‌ها را نگهداری کنند.


ب) از تعریف خواص Boolean با نام‌های منفی پرهیز کنید

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

namespace Refactoring.Day11.RemoveDoubleNegative.Before
{
public class Customer
{
public decimal Balance { get; set; }

public bool IsNotFlagged
{
get { return Balance > 30m; }
}
}
}

همچنین از تعریف این نوع خواص در فایل‌های کانفیگ برنامه‌ها نیز جدا پرهیز کنید؛ چون عموما کاربران برنامه‌ها با این نوع نامگذاری‌های منفی، مشکل مفهومی دارند.
Refactoring قطعه کد فوق بسیار ساده است و تنها با معکوس کردن شرط و نحوه‌ی نامگذاری خاصیت IsNotFlagged پایان می‌یابد:

namespace Refactoring.Day11.RemoveDoubleNegative.After
{
public class Customer
{
public decimal Balance { get; set; }

public bool IsFlagged
{
get { return Balance <= 30m; }
}
}
}

مطالب
استفاده از الگوی Adapter در تزریق وابستگی‌ها

در بعضی از مواقع ممکن است که در هنگام استفاده از اصل تزریق وابستگی‌ها، با یک مشکل روبرو شویم و آن این است که اگر از کلاسی استفاده می‌کنیم که به سورس آن دسترسی نداریم، نمی‌توانیم برای آن یک Interface تهیه کنیم و اصل (Depend on abstractions, not on concretions) از بین می‌رود، حال چه باید کرد.
برای اینکه موضوع تزریق وابستگی‌ها (DI) به صورت کامل در قسمتهای دیگر سایت توضیح داده شده است، دوباره آن را برای شما بازگو نمی‌کنیم .
لطفا به کد‌های ذیل توجه کنید:

کد بدون تزریق وابستگیها

به سازنده کلاس ProductService و تهیه یک نمونه جدید از وابستگی مورد نیاز آن دقت نمائید:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

namespace ASPPatterns.Chap2.Service
{
    public class Product
    {
    }

    public class ProductRepository
    {
        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products = new List<Product>();
            // Database operation to populate products …
            return products;
        }
    }

    public class ProductService
    {
        private ProductRepository _productRepository;
        public ProductService()
        {
            _productRepository = new ProductRepository();
        }

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products;
            string storageKey = string.Format("products_in_category_id_{0}", categoryId);
            products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
            if (products == null)
            {
                products = _productRepository.GetAllProductsIn(categoryId);
                HttpContext.Current.Cache.Insert(storageKey, products);
            }
            return products;
        }
    }
}

همان کد با تزریق وابستگی

using System;
using System.Collections.Generic;

namespace ASPPatterns.Chap2.Service
{
    public interface IProductRepository
    {
        IList<Product> GetAllProductsIn(int categoryId);
    }

    public class ProductRepository : IProductRepository
    {
        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products = new List<Product>();
            // Database operation to populate products …
            return products;
        }
    }

    public class ProductService
    {
        private IProductRepository _productRepository;
        public ProductService(IProductRepository  productRepository)
        {
            _productRepository = productRepository;
        }

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            //…
        }
    }
}
همانطور که ملاحظه می‌کنید به علت دسترسی به سورس، به راحتی برای استفاده از کلاس ProductRepository در کلاس ProductService، از تزریق وابستگی‌ها استفاده کرده‌ایم.
اما از این جهت که شما دسترسی به سورس Http context class را ندارید، نمی‌توانید به سادگی یک Interface را برای آن ایجاد کنید و سپس یک تزریق وابستگی را مانند کلاس ProductRepository برای آن تهیه نمائید.
خوشبختانه این مشکل قبلا حل شده است و الگویی که به ما جهت پیاده سازی آن کمک کند، وجود دارد و آن الگوی آداپتر (Adapter Pattern)  می‌باشد.
این الگو عمدتا برای  ایجاد یک Interface از یک کلاس به صورت یک Interface سازگار و قابل استفاده می‌باشد. بنابراین می‌توانیم این الگو را برای تبدیل HTTP Context caching API به یک API سازگار و قابل استفاده به کار ببریم.
در ادامه می‌توان Interface سازگار جدید را در داخل productservice که از اصل تزریق وابستگی‌ها (DI ) استفاده می‌کند تزریق کنیم.

یک اینترفیس جدید را با نام ICacheStorage به صورت ذیل ایجاد می‌کنیم:

public interface ICacheStorage
{
    void Remove(string key);
    void Store(string key, object data);
    T Retrieve<T>(string key);
}
حالا که شما یک اینترفیس جدید دارید، می‌توانید کلاس produceservic را به شکل ذیل به روز رسانی کنید تا از این اینترفیس، به جای HTTP Context استفاده کند.
public class ProductService
{
    private IProductRepository _productRepository;
    private ICacheStorage _cacheStorage;
    public ProductService(IProductRepository  productRepository,
    ICacheStorage cacheStorage)
    {
        _productRepository = productRepository;
        _cacheStorage = cacheStorage;
    }

    public IList<Product> GetAllProductsIn(int categoryId)
    {
        IList<Product> products;
        string storageKey = string.Format("products_in_category_id_{0}", categoryId);
        products = _cacheStorage.Retrieve<List<Product>>(storageKey);
        if (products == null)
        {
            products = _productRepository.GetAllProductsIn(categoryId);
            _cacheStorage.Store(storageKey, products);
        }
        return products;
    }
}
مسئله ای که در اینجا وجود دارد این است که HTTP Context Cache API صریحا نمی‌تواند Interface ایی که ما ایجاد کرده‌ایم را اجرا کند.
پس چگونه الگوی Adapter می‌تواند به ما کمک کند تا از این مشکل خارج شویم؟
هدف این الگو به صورت ذیل در GOF مشخص شده است .«تبدیل  Interface از یک کلاس به یک Interface مورد انتظار Client»
تصویر ذیل، مدل این الگو را به کمک UML نشان می‌دهد:
 

همانطور که در این تصویر ملاحظه می‌کنید، یک Client ارجاعی به یک Abstraction در تصویر (Target) دارد (ICacheStorage در کد نوشته شده). کلاس Adapter اجرای Target را بر عهده دارد و به سادگی متدهای Interface را نمایندگی می‌کند. در اینجا کلاس Adapter، یک نمونه از کلاس Adaptee را استفاده می‌کند و در هنگام اجرای قراردادهای Target، از این نمونه استفاده خواهد کرد.

اکنون کلاس‌های خود را در نمودار UML قرار می‌دهیم که به شکل ذیل آنها را ملاحظه می‌کنید.
 


در شکل ملاحظه می‌نمایید که یک کلاس جدید با نام HttpContextCacheAdapter مورد نیاز است. این کلاس یک کلاس روکش (محصور کننده یا Wrapper) برای متدهای HTTP Context cache است. برای اجرای الگوی Adapter کلاس HttpContextCacheAdapter را به شکل ذیل ایجاد می‌کنیم:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace ASPPatterns.Chap2.Service
{
    public class HttpContextCacheAdapter : ICacheStorage
    {
        public void Remove(string key)
        {
            HttpContext.Current.Cache.Remove(key);
        }

        public void Store(string key, object data)
        {
            HttpContext.Current.Cache.Insert(key, data);
        }

        public T Retrieve<T>(string key)
        {
            T itemStored = (T)HttpContext.Current.Cache.Get(key);
            if (itemStored == null)
                itemStored = default(T);
            return itemStored;
        }
    }
}
حال به سادگی می‌توان یک caching solution دیگر را پیاده سازی کرد بدون اینکه در کلاس ProductService  اثر یا تغییری ایجاد کند .

منبع : Professional Asp.Net Design Pattern
 
نظرات مطالب
ثبت جزئیات استثناهای Entity framework توسط ELMAH
یک نکته‌ی تکمیلی: معادل این مطلب با EF Core 5x

کدهای Interceptor به این صورت اصلاح شده و تطابق خواهند یافت:
using System;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;

namespace Utils
{
    public class EfExceptionsInterceptor : DbCommandInterceptor
    {
        private readonly ILogger<EfExceptionsInterceptor> _logger;

        public EfExceptionsInterceptor(ILogger<EfExceptionsInterceptor> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public override void CommandFailed(DbCommand command, CommandErrorEventData eventData)
        {
            logError(command, eventData);
        }

        public override Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken = default)
        {
            logError(command, eventData);
            return Task.CompletedTask;
        }

        private void logError(DbCommand command, CommandErrorEventData eventData)
        {
            if (command == null || eventData == null)
            {
                return;
            }

            var ex = eventData.Exception;
            if (ex == null)
            {
                return;
            }

            var sqlData = logSqlAndParameters(command);
            var contextualMessage = $"{sqlData}{Environment.NewLine}OriginalException:{Environment.NewLine}{ex} {Environment.NewLine}";
            _logger.LogError(contextualMessage);
        }

        private static string logSqlAndParameters(DbCommand command)
        {
            // -- Name: [Value] (Type = {}, Direction = {}, IsNullable = {}, Size = {}, Precision = {} Scale = {})
            var builder = new StringBuilder();

            var commandText = command.CommandText ?? "<null>";
            builder.AppendFormat(CultureInfo.InvariantCulture, "{0}Command: {0}{1}", Environment.NewLine, commandText)
                .AppendLine();

            var parameters = command.Parameters.OfType<DbParameter>().ToList();

            if (parameters.Any())
            {
                builder.AppendFormat(CultureInfo.InvariantCulture, "{0}Parameters: ", Environment.NewLine)
                    .AppendLine();
            }

            foreach (var parameter in parameters)
            {
                builder.Append("-- ")
                    .Append(parameter.ParameterName)
                    .Append(": '")
                    .Append((parameter.Value == null || parameter.Value == DBNull.Value) ? "null" : parameter.Value)
                    .Append("' (Type = ")
                    .Append(parameter.DbType);

                if (parameter.Direction != ParameterDirection.Input)
                {
                    builder.Append(", Direction = ").Append(parameter.Direction);
                }

                if (!parameter.IsNullable)
                {
                    builder.Append(", IsNullable = false");
                }

                if (parameter.Size != 0)
                {
                    builder.Append(", Size = ").Append(parameter.Size);
                }

                if (((IDbDataParameter)parameter).Precision != 0)
                {
                    builder.Append(", Precision = ").Append(((IDbDataParameter)parameter).Precision);
                }

                if (((IDbDataParameter)parameter).Scale != 0)
                {
                    builder.Append(", Scale = ").Append(((IDbDataParameter)parameter).Scale);
                }

                builder.Append(')').Append(Environment.NewLine);
            }

            return builder.ToString();
        }
    }
}
سپس برای ثبت آن، چون یک سرویس را به صورت تزریق وابستگی‌ها دریافت کرده، یکبار باید آن‌را به سیستم تزریق وابستگی‌ها معرفی کرد:
services.AddScoped<EfExceptionsInterceptor>();
و بعد نحوه‌ی معرفی آن به AddDbContextPool به روش خاص زیر است که از سرویس‌پروایدر استفاده می‌کند:
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
                    optionsBuilder
                        .UseSqlServer(
                            connectionString,
                            sqlServerOptionsBuilder =>
                            {
                              // ...
                            })
.AddInterceptors(serviceProvider.GetRequiredService<EfExceptionsInterceptor>()));
این نکته‌ای است که جهت ثبت Interceptorهای دارای تزریق وابستگی، مورد استفاده قرار می‌گیرد.
مطالب
آشنایی با Should Library
 نوشتن Assert در کد‌های تست، وابستگی مستقیم به انتخاب کتابخانه تست دارد. برای مثال:
NUnit:
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
 
namespace TestLibrary
{
   [TestFixture]
   public class MyTest
   {
       [Test]
       public void Test1()
       {
           var expectedValue = 2;
           Assert.That(expectedValue , Is.EqualTo(2));
       }
   }
}

Microsoft UnitTesting :

using Microsoft.VisualStudio.TestTools.UnitTesting ;
 
namespace TestLibrary
{
   [TesClass]
   public class MyTest
   {
       [TestMethod]
       public void Test1()
       {
           var expectedValue = 2;
           Assert.AreEqual (expectedValue , 2);
       }
   }
}
کد‌های Assert نوشته شده در مثال بالا با توجه به فریم ورک مورد استفاده متفاوت است. در حالی که کتابخانه Should، مجموعه ای از Extension Method هاست برای قسمت Assert در UnitTest‌های نوشته شده. با استفاده از این کتابخانه دیگر نیازی به نوشتن Assert به سبک و سیاق فعلی نیست. کدهای Assert بسیار خواناتر و قابل درک خواهند بود و از طرفی وابستگی به سایر کتابخانه‌های تست از بین خواهد رفت.
نکته: مورد استفاده این کتابخانه فقط در قسمت Assert کد‌های تست است و استفاده از سایر کتابخانه‌های جانبی الزامی است.
این کتابخانه به دو صورت مورد استفاده قرار می‌گیرد:
»Standard  که باید از Should.dll استفاده نمایید؛
»Fluent که باید از Should.Fluent.dll استفاده نمایید؛(پیاده سازی همان فریم ورک Should به صورت Static Reflection)

نصب  کتابخانه Should با استفاده از nuget (آخرین نسخه آن در حال حاضر 1.1.20 است ) :
Install-Package Should
  نصب کتابخانه Should.Fluent با استفاده از nuget(آخرین نسخه آن در حال حاضر 1.1.19 است):
Install-Package ShouldFluent

در ابتدا همان مثال قبلی را با این کتابخانه بررسی خواهیم کرد:
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace TestLibrary
{
   [TesClass]
   public class MyTest
   {
       [TestMethod]
       public void Test1()
       {          
           var expectedValue = 2;
           expectedValue.Should().Equal( 2 );
       }
   }
}
در نگاه اول چیز خاصی به چشم نمی‌خورد، اما اگر از این پس قصد داشته باشیم کد‌های تست خود را تحت فریم ورک NUnit پیاده سازی کنیم در قسمت Assert کد‌های خود هیچ گونه خطایی را مشاهده نخواهیم کرد.

مثال:
[TestMethod]        
public void AccountConstructorTest()        
{
     const int expectedBalance = 1000;    
     Account bankAccount = new Account();       
         
     // Assert.IsNotNull(bankAccount, "Account was null.");         
    // Assert.AreEqual(expectedBalance, bankAccount.AccountBalance, "Account balance not mathcing");         
bankAccount.ShouldNotBeNull("Account was null"); bankAccount.AccountBalance.ShouldEqual(expectedBalance, "Account balance not matching"); } 
در مثال بالا ابتدا با استفاده از Ms UnitTesting دو Assert نوشته شده است سپس در خطوط بعدی همان دو شرط با استفاده از کتابخانه Should نوشتم. در ذیل چند مثال از استفاده این کتابخانه (البته نوع Fluent آن) در هنگام کار با رشته ها، آبجکت ها، boolean و Collection‌ها را بررسی خواهیم کرد:

#Should.Fluent
public void Should_fluent_assertions()
{
    object obj = null;
    obj.Should().Be.Null();

    obj = new object();
    obj.Should().Be.OfType(typeof(object));
    obj.Should().Equal(obj);
    obj.Should().Not.Be.Null();
    obj.Should().Not.Be.SameAs(new object());
    obj.Should().Not.Be.OfType<string>();
    obj.Should().Not.Equal("foo");

    obj = "x";
    obj.Should().Not.Be.InRange("y", "z");
    obj.Should().Be.InRange("a", "z");
    obj.Should().Be.SameAs("x");

    "This String".Should().Contain("This");
    "This String".Should().Not.Be.Empty();
    "This String".Should().Not.Contain("foobar");

    false.Should().Be.False();
    true.Should().Be.True();

    var list = new List<object>();
    list.Should().Count.Zero();
    list.Should().Not.Contain.Item(new object());

    var item = new object();
    list.Add(item);
    list.Should().Not.Be.Empty();
    list.Should().Contain.Item(item);
};

#مثال‌های استفاده از متغیر‌های DateTime و Guid
public void Should_fluent_assertions()
{   
    var id = new Guid();
    id.Should().Be.Empty();

    id = Guid.NewGuid();
    id.Should().Not.Be.Empty();

    var date = DateTime.Now;
    date1.Should().Be.Today();

    var str = "";
    str.Should().Be.NullOrEmpty();                

    var one = "1";
    one.Should().Be.ConvertableTo<int>();

    var idString = Guid.NewGuid().ToString();
    idString.Should().Be.ConvertableTo<Guid>();
}


مطالب
راهی از گوگل ریدر به گوگل پلاس و سپس به دنیای خارج!

از امروز قابلیت‌های اجتماعی گوگل ریدر با گوگل پلاس یکی شده و به همین جهت یک سری از امکانات قدیمی آن حذف گردیده‌اند؛ مانند به اشتراک گذاری و لایک زدن و غیره و تمام این‌ها با دکمه‌ی به علاوه یک گوگل پلاس جایگزین شده‌اند. اینبار می‌شود علاقمند‌ی‌ها را از گوگل ریدر به حلقه‌های گوگل پلاس هدایت کرد. همه‌ی این‌ها خوب؛ اما سیستم به اشتراک گذاری‌های روزانه‌ی من رو به هم ریخته این‌ کارها! قبلا از حاصل اشتراک‌ها در گوگل ریدر، یک فید تهیه می‌شد که الان دیگر وجود خارجی ندارد. هیچکدام از حلقه‌های گوگل پلاس هم فید ندارند. به این ترتیب این محصول، تبدیل به یک فیدخوان معمولی شده است. مثل 100ها برنامه‌ی مشابه دیگر.

اما ... در ادامه ببینیم که چطور می‌توان از گوگل ریدر جدید، راهی به خارج باز کرد!

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


ب) اکنون به اکانت گوگل پلاس خود مراجعه کنید. در قسمت پروفایل، به لینک بالای صفحه دقت نمائید. چیزی است شبیه به:

https://plus.google.com/u/0/userId/posts 

این userId برای ادامه کار مهم است.

ج) از این userId برای فراخوانی لینک زیر استفاده خواهیم کرد (userId آن باید جایگزین شود):

https://plus.google.com/_/stream/getactivities/?&sp=[1,2,"userId",null,null,null,null,"social.google.com",[]]


خروجی آن طبق معمول متداول گوگل، فرمت JSON است و می‌توان از آن لیست اشتراک‌های عمومی را استخراج کرد. برای نمونه کلاس GooglePlusJsonParser تهیه شده زیر می‌تواند این عملیات را انجام دهد:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
//needs a ref. to System.Web.Extensions asm.
using System.Web.Script.Serialization;

namespace GooglePlus
{
[DebuggerDisplay("{DateTime}-{Title}-{Comment}-{Url}")]
public class GooglePlusItem
{
public DateTime DateTime { set; get; }
public string Url { set; get; }
public string Title { set; get; }
public string Comment { set; get; }
}

public static class Utils
{
public static DateTime ConvertFromUnixTimestamp(this long data)
{
return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(data);
}

public static string ToSafeString(this object data)
{
return data == null ? string.Empty : data.ToString();
}
}

public class GooglePlusJsonParser
{
public static IList<GooglePlusItem> GetMyPublicItemsList(string userId)
{
var url = string.Format("https://plus.google.com/_/stream/getactivities/?&sp=[1,2,\"{0}\",null,null,null,null,\"social.google.com\",[]]", userId);
var jsonData = new WebClient().DownloadString(url);
return ParsePublicData(jsonData);
}

public static IList<GooglePlusItem> ParsePublicData(string jsonData)
{
jsonData = removeRedundantData(jsonData);
var posts = parseJson(jsonData);
return createItemsList(posts);
}

private static IList<GooglePlusItem> createItemsList(object[] posts)
{
var result = new List<GooglePlusItem>();
foreach (object[] post in posts)
{
var items = (object[])((object[])post[66])[0];
result.Add(new GooglePlusItem
{
DateTime = (((Int64)post[5]) / 1000).ConvertFromUnixTimestamp(),
Title = items[3].ToSafeString(),
Url = items[1].ToSafeString(),
Comment = post[4].ToSafeString()
});
}
return result;
}

private static object[] parseJson(string jsonData)
{
var jsonObject = new JavaScriptSerializer().Deserialize<object[]>(jsonData);
var posts = (object[])(((object[])jsonObject[1])[0]);
return posts;
}

private static string removeRedundantData(string jsonData)
{
return jsonData.Replace(")]}'", string.Empty)
.Replace("[,", "[\"\",")
.Replace(",,", ",\"\",")
.Replace(",,", ",\"\",");
}
}
}

نحوه استفاده از آن هم می‌تواند فراخوانی متد GooglePlusJsonParser.GetMyPublicItemsList به همراه userId یاده شده باشد.



ایده اصلی از:
Getting the Google+ Feed for any profile in JSON


مطالب
تبدیل صفحات یک فایل PDF به تصویر، با استفاده از Acrobat SDK
با استفاده از اشیاء Com همراه با Acrobat SDK می‌توان تمام صفحات یک فایل PDF را تبدیل به تصویر کرد. این SDK به همراه نگارش کامل Adobe Acrobat نیز بر روی سیستم نصب می‌شود و یا می‌توان آن‌را به صورت جداگانه از سایت Adobe دریافت کرد.
پس از آن، برای تبدیل صفحات یک فایل PDF به تصویر، مراحل زیر باید طی شود:
الف) وهله سازی از شیء AcroExch.PDDoc
در صورتیکه SDK یاد شده بر روی سیستم نصب نباشد، این وهله سازی با شکست مواجه خواهد شد و همچنین باید دقت داشت که این SDK به همراه نگارش رایگان Adobe reader ارائه نمی‌شود.
ب) گشودن فایل PDF به کمک شیء Com وهله سازی شده (pdfDoc.Open)
ج) دریافت اطلاعات صفحه مورد نظر (pdfDoc.AcquirePage)
د) کپی این اطلاعات به درون clipboard ویندوز (pdfPage.CopyToClipboard)
به این ترتیب به یک تصویر Bmp قرار گرفته شده در clipboard ویندوز خواهیم رسید
ه) مرحله بعد تغییر ابعاد و ذخیره سازی این تصویر نهایی است.

کدهای زیر، روش انجام این مراحل را بیان می‌کنند:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Acrobat; //Add a Com Object ref. to "Adobe Acrobat 10.0 Type Library" => Program Files\Adobe\Acrobat 10.0\Acrobat\acrobat.tlb
using Microsoft.Win32;

namespace PdfThumbnail.Lib
{
    public static class PdfToImage
    {
        const string AdobeObjectsErrorMessage = "Failed to create the PDF object.";
        const string BadFileErrorMessage = "Failed to open the PDF file.";
        const string ClipboardError = "Failed to get the image from clipboard.";
        const string SdkError = "This operation needs the Acrobat SDK(http://www.adobe.com/devnet/acrobat/downloads.html), which is combined with the full version of Adobe Acrobat.";

        public static byte[] PdfPageToPng(string pdfFilePath, int thumbWidth = 600, int thumbHeight = 750, int pageNumber = 0)
        {
            byte[] imageData = null;
            runJob((pdfDoc, pdfRect) =>
                {
                    imageData = pdfPageToPng(thumbWidth, thumbHeight, pageNumber, pdfDoc, pdfRect);
                }, pdfFilePath);
            return imageData;
        }

        public static void AllPdfPagesToPng(Action<byte[], int, int> dataCallback, string pdfFilePath, int thumbWidth = 600, int thumbHeight = 750)
        {
            runJob((pdfDoc, pdfRect) =>
                {
                    var numPages = pdfDoc.GetNumPages();
                    for (var pageNumber = 0; pageNumber < numPages; pageNumber++)
                    {
                        var imageData = pdfPageToPng(thumbWidth, thumbHeight, pageNumber, pdfDoc, pdfRect);
                        dataCallback(imageData, pageNumber + 1, numPages);
                    }
                }, pdfFilePath);
        }

        static void runJob(Action<CAcroPDDoc, CAcroRect> job, string pdfFilePath)
        {
            if (!File.Exists(pdfFilePath))
                throw new InvalidOperationException(BadFileErrorMessage);

            var acrobatPdfDocType = Type.GetTypeFromProgID("AcroExch.PDDoc");
            if (acrobatPdfDocType == null || !isAdobeSdkInstalled)
                throw new InvalidOperationException(SdkError);

            var pdfDoc = (CAcroPDDoc)Activator.CreateInstance(acrobatPdfDocType);
            if (pdfDoc == null)
                throw new InvalidOperationException(AdobeObjectsErrorMessage);

            var acrobatPdfRectType = Type.GetTypeFromProgID("AcroExch.Rect");
            var pdfRect = (CAcroRect)Activator.CreateInstance(acrobatPdfRectType);

            var result = pdfDoc.Open(pdfFilePath);
            if (!result)
                throw new InvalidOperationException(BadFileErrorMessage);

            job(pdfDoc, pdfRect);

            releaseComObjects(pdfDoc, pdfRect);
        }

        public static byte[] ResizeImage(this Image image, int thumbWidth, int thumbHeight)
        {
            var srcWidth = image.Width;
            var srcHeight = image.Height;
            using (var bmp = new Bitmap(thumbWidth, thumbHeight, PixelFormat.Format32bppArgb))
            {
                using (var gr = Graphics.FromImage(bmp))
                {
                    gr.SmoothingMode = SmoothingMode.HighQuality;
                    gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    gr.CompositingQuality = CompositingQuality.HighQuality;
                    gr.InterpolationMode = InterpolationMode.High;

                    var rectDestination = new Rectangle(0, 0, thumbWidth, thumbHeight);
                    gr.DrawImage(image, rectDestination, 0, 0, srcWidth, srcHeight, GraphicsUnit.Pixel);

                    using (var memStream = new MemoryStream())
                    {
                        bmp.Save(memStream, ImageFormat.Png);
                        return memStream.ToArray();
                    }
                }
            }
        }

        static bool isAdobeSdkInstalled
        {
            get
            {
                return Registry.ClassesRoot.OpenSubKey("AcroExch.PDDoc", writable: false) != null;
            }
        }

        private static Bitmap pdfPageToBitmap(int pageNumber, CAcroPDDoc pdfDoc, CAcroRect pdfRect)
        {
            var pdfPage = (CAcroPDPage)pdfDoc.AcquirePage(pageNumber);
            if (pdfPage == null)
                throw new InvalidOperationException(BadFileErrorMessage);

            var pdfPoint = (CAcroPoint)pdfPage.GetSize();

            pdfRect.Left = 0;
            pdfRect.right = pdfPoint.x;
            pdfRect.Top = 0;
            pdfRect.bottom = pdfPoint.y;

            pdfPage.CopyToClipboard(pdfRect, 0, 0, 100);

            Bitmap pdfBitmap = null;
            var thread = new Thread(() =>
            {
                var data = Clipboard.GetDataObject();
                if (data != null && data.GetDataPresent(DataFormats.Bitmap))
                    pdfBitmap = (Bitmap)data.GetData(DataFormats.Bitmap);
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            thread.Join();

            Marshal.ReleaseComObject(pdfPage);

            return pdfBitmap;
        }

        private static byte[] pdfPageToPng(int thumbWidth, int thumbHeight, int pageNumber, CAcroPDDoc pdfDoc, CAcroRect pdfRect)
        {
            var pdfBitmap = pdfPageToBitmap(pageNumber, pdfDoc, pdfRect);
            if (pdfBitmap == null)
                throw new InvalidOperationException(ClipboardError);

            var pdfImage = pdfBitmap.GetThumbnailImage(thumbWidth, thumbHeight, null, IntPtr.Zero);
            // (+ 7 for template border)
            var imageData = pdfImage.ResizeImage(thumbWidth + 7, thumbHeight + 7);
            return imageData;
        }

        private static void releaseComObjects(CAcroPDDoc pdfDoc, CAcroRect pdfRect)
        {
            pdfDoc.Close();
            Marshal.ReleaseComObject(pdfRect);
            Marshal.ReleaseComObject(pdfDoc);
        }
    }
}
و برای استفاده از آن خواهیم داشت:
using System;
using System.IO;
using System.Windows.Forms;
using PdfThumbnail.Lib;

namespace PdfThumbnail
{
    class Program
    {
        static void Main(string[] args)
        {
            var pdfPath = Application.StartupPath + @"\test.pdf";
            PdfToImage.AllPdfPagesToPng((pageImageData, pageNumber, numPages) =>
                {
                    Console.WriteLine("Page {0}/{1}", pageNumber, numPages);
                    File.WriteAllBytes(string.Format("{0}\\page-{1}.png", Application.StartupPath, pageNumber), pageImageData);
                }, pdfPath);
        }
    }
}

کدهای این قسمت را از اینجا نیز می‌توانید دریافت کنید:
PdfThumbnail.zip
 
مطالب
متد جدید Order در دات نت 7
دات نت 7 به همراه دو متد جدید Order و OrderDescending است که مرتب سازی مجموعه‌های ساده را انجام می‌دهند.


روش متداول مرتب سازی مجموعه‌های ساده تا پیش از دات نت 7

فرض کنید لیستی از اعداد را داریم:
var numbers = new List<int> { -7, 1, 5, -6 };
تا پیش از دات نت 7 با استفاده از متدهای OrderBy و OrderByDescending موجود به همراه LINQ، امکان مرتب سازی صعودی و نزولی این لیست وجود دارد:
var sortedNumbers1 = numbers.OrderBy(n => n);
var sortedNumbers2 = numbers.OrderByDescending(n => n);
که در اینجا ذکر پارامتر keySelector ضروری است:
public static IOrderedEnumerable<TSource> OrderBy<TSource,TKey>(
   [NotNull] this IEnumerable<TSource> source,
   [NotNull] Func<TSource,TKey> keySelector)
هرچند می‌شد طراحی آن ساده‌تر باشد و حداقل برای مجموعه‌های ساده، نیازی به ذکر آن نباشد.


روش جدید مرتب سازی مجموعه‌های ساده در دات نت 7

دات نت 7 به همراه دو متد جدید Order و OrderDescending است که دیگر نیازی به ذکر پارامتر keySelector ذکر شده را ندارند:
var sortedNumbers3 = numbers.Order();
var sortedNumbers4 = numbers.OrderDescending();
و امضای آن‌ها به صورت زیر است:
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source)
public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source)
که در حقیقت دو متد الحاقی جدید قابل اعمال بر روی انواع و اقسام IEnumerableها هستند.


در مورد سایر مجموعه‌های پیچیده چطور؟

فرض کنید کلاس User را:
public class User
{
   public string Name { set; get; }
   public int Age { set; get; }
}
 به همراه لیستی از آن تعریف کرده‌ایم:
List<User> users = new()
                           {
                               new User { Name = "User 1", Age = 34 },
                               new User { Name = "User 2", Age = 24 },
                           };
سؤال: آیا اگر متد Order را بر روی این لیست فراخوانی کنیم:
var orderedUsers = users.Order();
برای مثال این مجموعه بر اساس نام و سن مرتب خواهد شد؟ که پاسخ آن خیر است و همچنین استثنائی را صادر می‌کند بر این مبنا که باید کلاس User، اینترفیس IComparable را پیاده سازی کند تا بتوان آن‌ها را مقایسه کرد؛ برای مثال چیزی شبیه به تغییرات زیر:
public class User : IComparable<User>
{
    public string Name { set; get; }
    public int Age { set; get; }

    public int CompareTo(User? other)
    {
        if (ReferenceEquals(this, other))
        {
            return 0;
        }

        if (ReferenceEquals(null, other))
        {
            return 1;
        }

        var nameComparison = string.Compare(Name, other.Name, StringComparison.Ordinal);
        if (nameComparison != 0)
        {
            return nameComparison;
        }

        return Age.CompareTo(other.Age);
    }
}
که در یک چنین مواردی شاید بهتر باشد از همان متد OrderBy پیشین استفاده کرد که الزامی به پیاده سازی اینترفیس IComparable را ندارد:
var orderedUsers2 = users.OrderBy(user => user.Name).ThenBy(user => user.Age);
نظرات مطالب
رسم نمودار توسط Kendo Chart
با سلام و تشکر
من مشکل مرتب سازی فیلد‌های از نوع عددی (int) در Kendo Chart دارم. همانند شکل زیر


کد‌های نوشته شده به شکل زیر هستند (Year از جنس عدد هست)

 $(document).ready(function () {
        //چارت 4
        $.ajax({
            type: 'POST',
            url: '@Url.Action("TierStatistics4", "Dashboard", new { area = "Tier" })', 
            dataType: 'json',
            data: { CostCenterId: $("#CostCenterId").val(), QueryIndex: $("#QueryIndex").val() },
            success: function (data) {
                dataSource = new kendo.data.DataSource({
                    data: data,
                    group: {
                        field: "Series"
                    },
                    sort:
                        {
                            field: "Year",
                            dir: "desc"
                        }
                });
                $("#chart4").kendoChart({
                    title: {
                        text: "آمار تعداد/هزینه(ریال) تایر مصرفی شعب به تفکیک مراکز هزینه و ماه‌های سال",
                        font: "irsans",
                    },
                    dataSource: dataSource,
                    series: [{
                        type: "column",
                        field: "Value",
                        categoryField: "Year",
                        name: "#=  group.value  #",
                    }],
                    categoryAxis: {

                        font: "irsans",
                        labels: {
                            font: "irsans"
                        }
                    },
                    valueAxis:
                        {
                            labels: {
                                font: "irsans",
                                visible: true,
                            }
                        },
                    seriesDefaults: {

                        style: "smooth",
                        labels: {
                            position: "Bottom",
                            visible: false,
                            fromat: "{0:n0}",
                            font: "irsans",
                            font: "irsans",
                            format: "{0:n0}",
                        }
                    },
                    legend: {
                        position: "top",
                        labels: {
                            font: "irsans"
                        }
                    },
                    tooltip: {
                        visible: true,
                        format: "{0:n0}",
                        template: "#= series.name #: #= value #",
                        font: "irsans",
                    }
                })
            },
            error: function (ex) {
                alert('خطا در بازیابی اطلاعات' + ex);
            }
        });
        return false;
    });
ضمنا ViewModel هم به صورت زیر می‌باشد
 public class DashoardViewModel : System.Object
    {
        public DashoardThirdViewModel()
        {
        }
        public string Series { get; set; }
        // **********
        public int Year { get; set; }
        //**********
        public long Value { get; set; }
        // **********


    }

ممنون از راهنمائی تون.
مطالب
بررسی مقدمات کتابخانه‌ی 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() }
    });