مطالب
ذخیره و بازیابی فایل در Mongodb (بخش سوم)
در قسمت‌های پیشین (^ ،^ ) در مورد عملیات CRUD در سطح دیتابیس و به طور کلی در مورد ایندکس گذاری صحبت کردیم. در این بخش قصد داریم یکی از موارد بسیار مهم، یعنی ذخیره‌ی فایل‌های باینری را در دیتابیس، مورد بررسی قرار دهیم. روش‌های مختلفی برای اینکار وجود دارند؛ ولی بعضی از این روش‌ها در حال حاضر منسوخ شده اعلام شده‌اند که در اینجا ما آخرین روش را که در حال حاضر هیچ ویژگی منسوخ شده‌ای ندارد، به کار می‌گیریم.
از آنجاکه نهایت اندازه‌ی یک سند BSON نمی‌تواند بیشتر از 16 مگابایت باشد، قابلیتی به نام GridFS ایجاد شده‌است تا بتوان فایل‌های باینری را در آن ذخیره کرد. GridFS شامل دو بخش مختلف برای ذخیره اطلاعات یک فایل باینری است:
- fs.chunks که برای ذخیره اطلاعات قطعه‌های یک فایل باینری به کار میرود.
- fs.files که برای ذخیره اطلاعات و متادیتاها به کار می‌رود.

قبل از هر چیزی باید بدانید که کتابخانه مربوط به GridFs در یک پکیج جداگانه عرضه شده است و باید آن از طریق nuget نصب کنیم:
install-package MongoDB.Driver.GridFS

برای آپلود یک فایل باینری به داخل سیستم از کد زیر استفاده میکنیم:
var client = new MongoClient();
var db = client.GetDatabase("publisher");
IGridFSBucket bucket = new GridFSBucket(db);
ابتدا یک شیء GridFsBucket را ایجاد میکنیم که از ما اطلاعات دیتابیس مورد نظر را برای ارسال فایل میخواهد و نتیجه‌ی آن یک کلاس از جنس اینترفیس IGridFSBucket می‌باشد. این باکت یا سطل در واقع همانند کالکشن رفتار میکند.
byte[] source=File.ReadAllBytes(@"D:\Untitled.png");
var options = new GridFSUploadOptions
            {
                ChunkSizeBytes = 64512, // 63KB
                Metadata = new BsonDocument
                {
                    { "CoverType", "Front" }, 
                    { "copyrighted", true }
                }
            };
در مرحله بعد فایل باینری را به صورت آرایه‌ای از بایت‌ها میخوانیم (البته حالت‌های مختلفی چون استریم را نیز پشتیبانی میکند). بعد از خواندن فایل، یک شیء از جنس کلاس GridFSUploadOptions را ایجاد و اطلاعات فایل آپلودی را مشخص می‌کنیم. به عنوان مثال اولین خصوصیتی که پر میکنیم خصوصیت تعیین سایز قطعات فایل باینری می‌باشد که به طور پیشفرض بر روی 64 مگابایت قرار گرفته است و عموما هم برای اکثر موارد، پاسخگوی نیاز‌ها است (در بخش بعدی مقالات، بیشتر این مورد را بررسی میکنیم).
مورد دومی که مقداردهی شده‌است، متادیتا‌ها هستند و این قابلیت را داریم که پرس و جوی خود را بر اساس آن‌ها نیز فیلتر کنیم. این خصوصیت مقدار دریافتی از جنس BsonDocument را دریافت میکند. ولی اگر شما برای فایل خود، کلاس اختصاصی برای متادیتاها در نظر گرفته‌اید میتوانید از یک Extension Method به نام ToBsonDocument استفاده کنید و شیء خود را به این نوع تبدیل کنید:
 var options = new GridFSUploadOptions
            {
                ChunkSizeBytes = 64512, // 63KB
                Metadata = metaData.ToBsonDocument()
            };


در نهایت آن را آپلود میکنیم:
 var id = bucket.UploadFromBytes("GoneWithTheWind", source, options);
پارامتر اول آن، نامی برای بسته آپلودی است. پارامتر دوم، خود فایل آپلودی میباشد و با پارمتر آخر هم تنظیماتی را که برای فایل مورد نظر توسط کلاس GridFSUploadOptions  تعیین کرده‌ایم، مشخص میکنیم. موقعیکه آپلود انجام شود، به ازای این کد، یک شناسه‌ی اختصاصی از جنس ObjectId را دریافت میکنیم که می‌توانیم آن را به یک خصوصیت در سند اصلی نسبت دهیم تا ارتباط بین سند و فایل هایش را داشته باشیم.

نکته: اگر در یک کلاس، چند فیلد از جنس ObjectId دارید، مونگو در بین تشخیص شناسه اصلی سند و شناسه تصاویر، با توجه به نام خصوصیت‌ها و غیره، تا حد زیادی هوشمند عمل میکند. ولی اگر خواستید صریحا شناسه اصلی را ذکر کنید و آن را متمایز از بقیه نشان دهید، می‌توانید از خصوصیت BsonId در بالای نام فیلد ID استفاده کنید:
[BsonId]
public ObjectId Id { get; set; }

جهت خواندن فایل آپلود شده، تنها کافی است از طریق شناسه‌ی دریافتی در مرحله‌ی آپلود، اقدام نماییم:
var bytes = bucket.DownloadAsBytes(id);

نکته: تمام متدهای آپلود و دانلود دیتا، هم به صورت آرایه ای از بایت‌ها و هم به صورت استریم میتوانند مورد استفاده قرار بگیرند و به ازای هر کدام، متدهای همزمان و غیرهمزمان نیز موجود هستند.


اگر قصد دارید بر اساس نام داده شده، فایل را دریافت کنید، ممکن است که چندین فایل، تحت یک نام ذخیره شده باشند که میتوانید در حالت‌های مختلفی این تصاویر را واکشی نمایید:
0 : فایل اصلی
1: اولین نسخه فایل
2: دومین نسخه فایل
و الی آخر...

1-: جدیدترین نسخه فایل (مقدار پیش فرض)
2-: نسخه ماقبل جدیدترین نسخه فایل
و الی آخر...


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


اولین تصویر، تصویر اصلی محسوب می‌شود و بعد از آن، نسخه اول و نسخه دوم تصویر، وارد سیستم می‌شوند و تکه کد زیر از آنجاکه با مقدار پیش فرض پر شده‌است باید آخرین تصویر، تصویر سمت چپ را برای شما بر روی دیسک ذخیره کند:
var client = new MongoClient();
var db = client.GetDatabase("publisher");
IGridFSBucket bucket = new GridFSBucket(db);
var image=bucket.DownloadAsBytesByName("City of Glass-cover");
File.WriteAllBytes(@"D:\a.jpg",image);

برای مقداردهی خواص بالا به شکل زیر عمل میکنیم:
var client = new MongoClient();
var db = client.GetDatabase("publisher");
IGridFSBucket bucket = new GridFSBucket(db);
var options = new GridFSDownloadByNameOptions
            {
                Revision = 0
            };
var image=bucket.DownloadAsBytesByName("City of Glass-cover",options);
File.WriteAllBytes(@"D:\a.jpg",image);
با پاس کردن مقدار 0 به مونگو، اولین تصویر وارد شده، یعنی تصویر اصلی را دریافت می‌کنیم که اولین تصویر از سمت راست می‌شود. اگر مقادیر 1 یا 1- را پاس دهیم، چون تنها سه تصویر بیشتر نیست، در هر دو حالت تصویر دوم بازگردانده می‌شود.

برای بازگردانی تصاویر از طریق مقادیر موجود در متادیتا، باید از کلاس ویژه‌ای به نام GridFSFileInfo استفاده کنیم. در اینجا هم همانند روزهای اول، از کلاس بیلدر جهت ایجاد شرط استفاده میکنیم:
var client = new MongoClient();
var db = client.GetDatabase("publisher");
IGridFSBucket bucket = new GridFSBucket(db);
var filter = Builders<GridFSFileInfo>.Filter.Gte(x => x.Length , 600);
var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);
var options =new GridFSFindOptions()
            {
                Limit = 3,
                Sort = sort
            };
var cursor = bucket.Find(filter, options);
var list = cursor.ToList();
در اینجا ابتدا مشخص کرده‌ایم که فایل مورد نظر باید حجمی بیشتر از 600 بایت داشته باشد و مرتب سازی آن به صورت نزولی، بر اساس زمان آپلود باشد. در عبارت لامبدا تعریف شده، می‌توانید خصوصیت‌های مختلف یک فایل از قبیل نام، حجم (سایز) ، زمان آپلود و ... را ببینید. سپس مرتب سازی و تعداد رکورد برگشتی از ابتدای جدول را مشخص میکنیم و از متد Find یا FindAsync جهت جست‌وجو استفاده میکنیم. با شکل گرفتن کوئری درخواست، لیستی از آن را تهیه میکنیم. مقدار بازگشتی این شیء، در واقع اسنادی از تصاویر هستند که میتوانید از طریق Id یا نام آن‌ها، فایل اصلی را واکشی نمایید.
برای یافتن تصاویر بر اساس متادیتاهای تعریف شده، از کد زیر استفاده میکنیم:
var filter = Builders<GridFSFileInfo>.Filter.Eq("metadata.CoverType","Front");

تغییر نام تصاویر
جهت ویرایش یک نام فایل از طریق متدهای زیر اقدام می‌نماییم:
bucket.Rename(id, newFilename);

//یا در حالت غیرهمزمان
await bucket.RenameAsync(id, newFilename);

حذف تصویر
bucket.Delete(id);

//یا
await bucket.DeleteAsync(id);

برای حذف کل bucket از طریق کد زیر اقدام می‌نماییم:
bucket.Drop();

//یا
await bucket.DropAsync();
 
در بخش بعدی Chunk را مورد بررسی قرار می‌دهیم.
مطالب
بررسی مقدمات کتابخانه‌ی 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() }
    });   
پاسخ به بازخورد‌های پروژه‌ها
فضای نام MVC
سلام؛ ممنون
حقیقتش من یک صفحه در codeplex برای این پروژه ایجاد کردم ولی چند دفعه موقع commit کردن پروژه، با مشکل مواجه می‌شوم و احتمالا دلیلش حجم بالای پروژه( نزدیک به 60 مگابایت) است.
ولی باز من پروژه را اصلاح کردم و این اصلاحات شامل تمامی موارد گزارش شده و یکم تمیز سازی کد و فضای نام‌ها و وابستگی به کتابخانه‌ی persia که اشتباها  در دو پروژه به آدرس مطلق در کامپیوترم رفرنس داده شده بود، می‌شود.
اکنون نیز می‌توانید نسخه‌ی جدید را از همین سایت در بخش فایل‌های پروژه دانلود کنید.
ولی باز هم اگر کسی از دوستان در اجرای پروژه مشکل داشت، همین جا اعلام کند تا پروژه را به صورت یکجا در هاست خودم آپلود کنم و اینجا  قرار بدهم.
موفق باشید...
نظرات اشتراک‌ها
راهنمای نظم بخشیدن به کدهای CSS
من این کتاب رو قبلاً خوندم. واقعاً عالیه و راهنمای بسیار خوبی برای نامگذاری و دسته بندی قواعد CSS هست.
پاسخ به بازخورد‌های پروژه‌ها
گزارش خطا
این DatePicker تاریخ امروز را محاسبه نمی‌کند. شما باید محاسبه کنید.
به راهنمای آن مراجعه کنید.

نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
 با سلام؛ شما در کلاس UploadFileService از این بسته یک متد با امضا زیر تعریف کردید:
 Task < bool > SavePostedFileAsync ( IFormFile formFile , string destinationDirectoryName , bool allowOverwrite );
متد فوق تصاویر را با نامی خاص (که توسط متد  getUniqueFilePath ( IFormFile formFile , string uploadsRootFolder )  )مشخص میشه ذخیره می‌کنه و مقدار True  رو بر می‌گردنه. مشکلی که وجود دارد عدم پاس دادن نام فایل برای ذخیره در دیتابیس و نمایش‌های بعدی هست.