مطالب
کار با دیتاتایپ JSON در MySQL - قسمت اول
تا قبل از اضافه شدن دیتاتایپ JSON به صورت توکار در MySQL، داده‌های JSON را تنها میتوانستیم با فرمت رشته‌ای، درون دیتابیس ذخیره کنیم: 
CREATE TABLE tableName (
jsonData CHAR(250) -- or VARCHAR, TEXT, BLOB
);

INSERT INTO tableName VALUES (
'{ "name": "User1", "age": 41}'
);

SELECT * FROM tableName;

{ "name": "User1", "age": 41}
اما مشکل اینجاست که هیچ نوع اعتبارسنجی بر روی این دیتا صورت نخواهد گرفت؛ هیچ روشی برای مطمئن شدن از اینکه تگ‌ها به درستی استفاده شده‌اند، وجود ندارد و همچنین امکان جستجو را سخت خواهد کرد؛ زیرا مجبور خواهیم بود از Regular Expressions برای جستجوی درون متن‌های ذخیره شده استفاده کنیم:
SELECT * FROM tableName
WHERE jsonData REGEXP 'User1';

از نسخه MySQL 5.7.8 به بعد، می‌توانیم از نوع داده JSON برای ذخیره‌سازی محتوای JSON، استفاده کنیم. از این دیتاتایپ برای ذخیره‌سازی یک JSON document معتبر میتوان استفاده کرد:
CREATE TABLE tableName (
jsonData JSON
);

INSERT INTO tableName VALUES (
'{ "name": "User1", "age": 41, "name": "User2"}'
);

SELECT * FROM tableName;

{"age": 41, "name": "User2"}

همانطور که مشاهده میکنید MySQL به صورت اتوماتیک یکسری نرمال‌سازی را روی دیتا اعمال کرده است:
  • ابتدا بررسی خواهد شد که سند JSON معتبر باشد؛ در غیر اینصورت ذخیره‌سازی با مشکل مواجه خواهد شد.
  • از فیلدهایی که کلید تکراری دارند، صرفنظر خواهند شد. در مثال بالا دوبار فیلد name را مقداردهی کرده‌ایم. در اینجالت key/value دوم لحاظ شده‌است. البته میبایستی اصل first key wins لحاظ میشد، اما این مورد به عنوان یک باگ گزارش شده‌است و در نسخه‌های 8 به بعد رفع شده‌است (https://forums.mysql.com/read.php?3,660500,660500 - https://bugs.mysql.com/bug.php?id=86866).
  • فاصله‌های اضافی بین کلیدها حذف شده‌اند.
  • برای جستجوی بهتر، کلیدهای آبجکت JSON به صورت مرتب شده ذخیره شده‌اند.

جستجو درون JSON Document
یک سند JSON، از یکسری کلیدها به همراه مقادیرشان تشکیل شده‌است. همچنین مقادیر میتوانند شامل اشیاء یا آرایه‌هایی به صورت تودرتو باشند. بنابراین به یک path جهت استخراج مقادیر نیاز خواهیم داشت. برای نوشتن یک path باید scope آن را تعیین کنیم که در توابع MySQL این scope به صورت پیش‌فرض، سند جاری میباشد که توسط علامت $ مشخص میشود. 
فرض کنید ساختار زیر را درون دیتابیس ذخیره کرده‌ایم:
{
    "id": "1",
    "sku": "asdf123",
    "name": "Lorem ipsum jacket",
    "price": 12.45,
    "discount": 10,
    "offerEnd": "October 5, 2020 12:11:00",
    "new": false,
    "rating": 4,
    "saleCount": 54,
    "category": ["fashion", "men"],
    "tag": ["fashion", "men", "jacket", "full sleeve"],
    "variation": [
      {
        "color": "white",
        "image": "/assets/img/product/fashion/1.jpg",
        "size": [
          {
            "name": "x",
            "stock": 3
          },
          {
            "name": "m",
            "stock": 2
          },
          {
            "name": "xl",
            "stock": 5
          }
        ]
      },
      {
        "color": "black",
        "image": "/assets/img/product/fashion/8.jpg",
        "size": [
          {
            "name": "x",
            "stock": 4
          },
          {
            "name": "m",
            "stock": 7
          },
          {
            "name": "xl",
            "stock": 9
          },
          {
            "name": "xxl",
            "stock": 1
          }
        ]
      },
      {
        "color": "brown",
        "image": "/assets/img/product/fashion/3.jpg",
        "size": [
          {
            "name": "x",
            "stock": 1
          },
          {
            "name": "m",
            "stock": 2
          },
          {
            "name": "xl",
            "stock": 4
          },
          {
            "name": "xxl",
            "stock": 0
          }
        ]
      }
    ],
    "image": [
      "/assets/img/product/fashion/1.jpg",
      "/assets/img/product/fashion/3.jpg",
      "/assets/img/product/fashion/6.jpg",
      "/assets/img/product/fashion/8.jpg",
      "/assets/img/product/fashion/9.jpg"
    ],
    "description": {
      "shortDescription": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur.",
      "fullDescription": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
    }
  }

برای دریافت دسته‌بندی‌های هر ردیف میتوانیم از تابع JSON_EXTRACT استفاده کنیم:
SELECT 
    JSON_PRETTY(
  JSON_EXTRACT(data, "$.category")
    )
FROM
    experiments.productMetadata;

/* 
  [
    "fashion",
    "men"
  ]
  [
    "fashion",
    "women"
  ]
  [
    "fashion",
    "men"
  ]
*/
همانطور که مشاهده میکنید، تابع JSON_EXTRACT یک آرگومان دومی را نیز دریافت میکند که توسط آن میتوانیم path موردنظر را وارد کنیم و همانطور که عنوان شد، از $ برای دسترسی به سند جاری استفاده میکنیم. سپس در ادامه نام پراپرتی‌ای را که میخواهیم استخراج کنیم، تعیین کرده‌ایم. در اینجا چون ساختار ذخیره شده، به صورت شیء میباشد، به صورت مستقیم از $ و بعد از آن نقطه و سپس نام پراپرتی استفاده کرده‌ایم. میتوانیم عمق پیمایش را نیز بیشتر کنیم. به عنوان مثال برای دسترسی به المنت دوم از آرایه tag درون دیتا خواهیم داشت:
JSON_EXTRACT(data, "$.tag[1]")
JSON_EXTRACT(data, "$.description.shortDescription")

همچنین اگر کلید مقداری را که میخواهیم جستجو کنیم، بدانیم اما از کلید والد آن اطلاع نداشته باشیم، میتوانیم از * استفاده کنیم: 
SELECT 
JSON_EXTRACT(data, "$.*.shortDescription")
FROM experiments.productMetadata;

JSON_KEYS
از این تابع جهت دریافت کلیدهای top level یک شیء JSON استفاده میشود:
SELECT 
JSON_KEYS(data)
FROM experiments.productMetadata;

-- ["id", "new", "sku", "tag", "name", "image", "price", "rating", "category", "discount", "offerEnd", "saleCount", "variation", "description"]
-- ["id", "new", "sku", "tag", "name", "image", "price", "rating", "category", "discount", "saleCount", "variation", "description"]


همچنین میتوانیم path را نیز به عنوان آرگومان دوم آن تعیین کنیم: 
SELECT 
JSON_KEYS(data, "$.description")
FROM experiments.productMetadata;

-- ["fullDescription", "shortDescription"]
-- ["fullDescription", "shortDescription"]

JSON_CONTAINS 
از این تابع برای جستجو استفاده خواهیم کرد و همانطور که از نام آن پیداست، در صورت وجود مقدار مورد جستجو، خروجی ۱ خواهد بود:
SELECT 
    JSON_CONTAINS(data, "10", "$.discount")
FROM
    experiments.productMetadata;

-- 1
-- 0

JSON_CONTAINS_PATH
توسط این تابع میتوانیم بررسی کنیم که یک path یا یک یکسری path خاص درون JSON document وجود دارند یا خیر: 
SELECT 
JSON_CONTAINS_PATH(data, "one", "$.description", "$.address", "$.website")
FROM experiments.productMetadata;
آرگومان اول این تابع، داکیومنتی است که میخواهیم جستجو کنیم. برای آرگومان دوم، یکی از دو مقدار one یا all را میتوانیم تنظیم کنیم. در ادامه لیستی از pathهایی را که میخواهیم جستجو کنیم، وارد کرده‌ایم. در حالت one، اگر تنها یکی از pathها درون داکیومنت JSON وجود داشته باشند، خروجی ۱ خواهد بود. اگر one را به all تنظیم کنیم، یعنی باید تمامی pathها، درون داکیومنت وجود داشته باشند تا خروجی ۱ شود؛ در غیراینصورت خروجی ۰ خواهد بود. 

JSON_SEARCH  
توسط این تابع میتوانیم position مقدار مورد جستجو را درون داکیومنت JSON پیدا کنیم: 
SELECT 
    JSON_SEARCH(data, 'one', 'fashion')
FROM
    experiments.productMetadata;
    
-- "$.tag[0]"
-- "$.tag[0]"

مطالب
معرفی Selector های CSS - قسمت 5
41- :checked
برای تگ‌های radio و checkbox استفاده می‌شود و آنهایی را انتخاب می‌کند که گزینه‌ی آن‌ها انتخاب شده است یا شامل ویژگی checked می‌باشند.
<style>
    :checked {
        width: 50px;
        height: 50px;
    }
</style>
<input type="checkbox" checked="checked"/>
<input type="checkbox"/>
<input type="radio" name="test"/>
<input type="radio" name="test" checked="checked"/>
<input type="radio" name="test"/>
در مثال فوق تمامی checkbox هایی که گزینه‌ی آن‌ها انتخاب شده‌اند با ابعاد 50 در 50 نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :checked  3

42- :indeterminate
در HTML نمی‌توان وضعیت indeterminate را برای checkbox ایجاد نمود. اما با جاوا اسکریپت می‌توان این وضعیت را ایجاد کرد. هر checkbox می‌تواند دارای سه وضعیت انتخاب شده، انتخاب نشده و نامعلوم (indeterminate) باشد. این Selector تگهای checkbox ی را انتخاب می‌نماید که وضعیت نامعلوم دارند.
<style>
    :indeterminate {
        width: 50px;
        height: 50px;
    }
</style>
<input type="checkbox" id="chk1"/>
<input type="checkbox" id="chk2" checked="checked"/>
<input type="checkbox" id="chk3"/>
<script>
    document.getElementById("chk1").indeterminate=true;
</script>
در مثال فوق chk1 به صورت indeterminate انتخاب شده است که با ابعاد 50 در 50 نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No   No   No  No 
 No :indeterminate  4

43- :default
برای تگ‌های radio و checkbox، آنهایی را انتخاب می‌کند که در زمان بارگزاری فرم و بصورت پیش فرض انتخاب شده‌اند. برای دکمه‌ی submit ایی نیز استفاده می‌شود که در حال حاضر در فرم فعال می‌باشد. پشتیبانی از این Selector در مرورگرها متفاوت می‌باشد؛ بنابراین در چند مرورگر کارآیی آن را بررسی کنید.
<style>
    :default {
        width: 50px;
        height: 50px;
        background: lime;
    }
</style>
<form>
    <input type="checkbox" checked="checked" />
    <input type="checkbox" />
    <input type="radio" name="radio1" />
    <input type="radio" name="radio1" checked="checked" />
    <input type="radio" name="radio1" />
    <input type="submit" value="Default" />
    <input type="submit" value="Submit 2" />
</form>
در مثال فوق checkbox و radio هایی که در لود اولیه فرم دارای ویژگی checked می‌باشند به همراه دکمه‌ی submit با عنوان Default با ابعاد 50 در 50 و رنگ پس زمینه‌ی لیمویی نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No   No   No  No 
 No :default  4

44- :root
المنت ریشه‌ی یک صفحه را انتخاب می‌نماید. معمولا تگ html به عنوان المنت ریشه انتخاب می‌شود.
<style>
    :root {
        background: khaki;
    }
</style>
در مثال فوق رنگ پس زمینه به رنگ خاکی نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :root  3

45- :after
جهت درج محتوا بعد از تگ استفاده می‌شود.
<style>
    form:after {
        content: "[ * : Required]";
        color: red;            
    }
        input+span:after {
            content: ' * ';
            color: red;
        }
</style>
<form>
    <div><input type="text" /><span></span></div>
    <div><input type="password" /><span></span></div>
    <div><input type="email" /><span></span></div>
</form>
در مثال فوق بعد از تمامی span‌های قرار گرفته در کنار تگ‌های input، یک کاراکتر * را به رنگ قرمز نمایش میدهد. بعد از فرم نیز رشته‌ی [ * : Required] را به رنگ قرمز نمایش می‌دهد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1 7.0  9.0 3.5  4.0 :after 2

46- :before
جهت درج محتوا قبل از تگ استفاده می‌شود.
<style>
    form:before {
        content: "[ * : Required]";
        color: red;
    }

    input + span:before {
        content: ' * ';
        color: red;
    }
</style>
<form>
    <div><input type="text" /><span></span></div>
    <div><input type="password" /><span></span></div>
    <div><input type="email" /><span></span></div>
</form>
در مثال فوق قبل از تمامی span‌های قرار گرفته در کنار تگ‌های input، یک کاراکتر * را به رنگ قرمز نمایش می‌دهد. قبل از فرم نیز رشته‌ی [ * : Required] را به رنگ قرمز نمایش می‌دهد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1 7.0  9.0 3.5  4.0 :before 2

47- ::selection
محتوایی را انتخاب می‌کند که در حال حاضر در صفحه انتخاب یا Select شده‌است.
<style>
    ::selection {
        background: navy;
        color: orange;
    }
</style>
<p>
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 
    Maecenas porttitor congue massa. Fusce posuere, 
    magna sed pulvinar ultricies, purus lectus malesuada libero, 
    sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est.
</p>
در مثال فوق با انتخاب متن توسط ماوس، رنگ زمینه‌ی متن انتخاب شده به صورت سرمه ای و رنگ متن آن نارنجی نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1  9.6  9.0 2.0
-moz-
 4.0 ::selection  در CSS 3 معرفی و سپس حذف شد ولی امکان بازگشت مجدد وجود دارد

48- :not(S1,S2)
تگ هایی را انتخاب می‌کند که شامل Selector‌های S1 یا S2 یا ... یا Sn نباشند.
<style>
    :not([readonly]) {
        background: yellow;
    }
</style>
<input type="text"/>
<input type="text" readonly="readonly"/>
<input type="text"/>
<input type="text" readonly="readonly" />
در مثال فوق پس زمینه‌ی تگهای input که ویژگی readonly ندارند به رنگ زرد نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :not(S1,S2)  3

49- :matches(S1,S2)
تگ هایی را انتخاب می‌کند که شامل Selector‌های S1 و/یا S2 و/یا ... و/یا Sn باشند. 
<style>
    :matches([readonly]) {
        background: yellow;
    }
</style>
<input type="text" />
<input type="text" readonly="readonly" />
<input type="text" />
<input type="text" readonly="readonly" />
در مثال فوق پس زمینه‌ی تگهای input که ویژگی readonly دارند به رنگ زرد نمایش می‌یابند. 
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No   No   No  No 
 No :matches(S1,S2)  4

50- :has(S1,S2)
تگ هایی را انتخاب می‌کند که در ارتباط خاصی با Selector های S1 و/یا S2 و/یا ... و/یا Sn می‌باشند. 
<style>
    :has(>span) {
        color: red;
    }
    :has(+div) {
        color: blue;
    }
</style>
<div>
    <p>
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
        Maecenas <span>porttitor</span> congue massa. Fusce posuere,
        magna sed pulvinar ultricies, purus lectus malesuada libero,
        sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est.
    </p>
    <h1>Header 1</h1>
    <div>
            
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
        Maecenas porttitor congue massa. Fusce posuere,
        magna sed pulvinar ultricies, purus lectus malesuada libero,
        sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est.            
    </div>
در مثال فوق متن موجود در تگ p که دارای فرزند مستقیم span می‌باشد، با توجه به Selector ی که بصورت :has(>span) تعریف شده است، به رنگ قرمز نمایش می‌یابد. همچنین Header 1 که دارای تگ div هم تراز و دقیقا بعد از h1 است، با توجه به Selector ی که بصورت  :has(+div) تعریف شده است، به رنگ آبی نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No   No   No  No 
 No :has(S1,S2)  4 
مطالب
ایجاد قالب‌های سفارشی ستون‌ها و فرمت شرطی اطلاعات در PdfReport
صورت مساله:
- لیستی از حقوق کارکنان را داریم. در گزارش نهایی آن نیاز است عدد حقوق کارکنانی با مبلغ کمتر از 1000، با رنگی دیگر نمایش داده شوند.
همچنین در این گزارش هر ردیفی که در ماه 7 واقع شده نیز ظاهر عدد سلول مربوط به آن ماه، به رنگ قهوه‌ای و زمینه زرد تغییر یابد.
- در ستون مشخصات افراد این گزارش، نیاز است تصویر کارمند به همراه نام او در ذیل این تصویر (داخل یک سلول) نمایش داده شوند.

چیزی شبیه به این گزارش!


مورد اول در گزارشات، اصطلاحا به conditional formatting معروف است و مورد دوم مرتبط است به تهیه قالب‌های سفارشی، بجای استفاده از قالب‌های سلول‌های پیش فرض PdfReport؛ که در ادامه نحوه انجام این موارد را بررسی خواهیم کرد.

ابتدا سورس کامل این مثال را ملاحظه نمائید:
using System;
using iTextSharp.text;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CustomCellTemplate
{
    public class CustomCellTemplatePdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.RightToLeft);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
                doc.Compression(new CompressionSettings
                               {
                                   CompressionLevel = CompressionLevel.BestCompression,
                                   EnableCompression = true
                               });
            })
             .DefaultFonts(fonts =>
             {
                 fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                            Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
             })
             .PagesFooter(footer =>
             {
                 footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
             })
             .PagesHeader(header =>
             {
                 header.DefaultHeader(defaultHeader =>
                 {
                     defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                     defaultHeader.Message("گزارش جدید ما");
                 });
             })
             .MainTableTemplate(template =>
             {
                 template.BasicTemplate(BasicTemplate.SnowyPineTemplate);
             })
             .MainTablePreferences(table =>
             {
                 table.ColumnsWidthsType(TableColumnWidthType.Relative);
                 table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                 {
                     ColumnsGap = 20,
                     ColumnsPerPage = 2,
                     ColumnsWidth = 250,
                     IsRightToLeft = true,
                     TopMargin = 7
                 });
             })
             .MainTableDataSource(dataSource =>
             {
                 var table = new System.Data.DataTable("لیست حقوق");
                 table.Columns.Add("شخص", typeof(string));
                 table.Columns.Add("ماه", typeof(int));
                 table.Columns.Add("مبلغ", typeof(decimal));

                 var rnd = new Random();
                 for (int i = 0; i < 200; i++)
                     table.Rows.Add("شخص " + i, rnd.Next(1, 12), rnd.Next(400, 2000));

                 dataSource.DataTable(table);
             })
             .MainTableEvents(events =>
             {
                 events.DataSourceIsEmpty(message: "There is no data available to display.");
                 events.CellCreated(args =>
                     {
                         //change the background color of the cell based on the value
                         if (args.RowType == RowType.DataTableRow && args.Cell.RowData.Value != null && args.Cell.RowData.Value is decimal)
                         {
                             if ((decimal)args.Cell.RowData.Value <= 1000)
                                 args.Cell.BasicProperties.BackgroundColor = BaseColor.CYAN;
                         }
                     });
             })
             .MainTableSummarySettings(summary =>
             {
                 summary.OverallSummarySettings("جمع کل");
                 summary.PageSummarySettings("جمع صفحه");
                 summary.PreviousPageSummarySettings("نقل از ستون قبل");
             })
             .MainTableColumns(columns =>
             {
                 columns.AddColumn(column =>
                 {
                     column.PropertyName("rowNo");
                     column.IsRowNumber(true);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(0);
                     column.Width(1);
                     column.HeaderCell("ردیف");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("شخص");
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(1);
                     column.Width(3);
                     column.HeaderCell("شخص");
                     column.ColumnItemsTemplate(t => t.CustomTemplate(new MyCustomCellTemplate()));
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("ماه");
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(2);
                     column.Width(2);
                     column.HeaderCell("ماه");
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.ConditionalFormatFormula(list =>
                         {
                             var cellValue = int.Parse(list.GetSafeStringValueOf("ماه", nullValue: "0"));
                             if (cellValue == 7)
                             {
                                 return new CellBasicProperties
                                 {
                                     PdfFontStyle = DocumentFontStyle.Bold | DocumentFontStyle.Underline,
                                     FontColor = new BaseColor(System.Drawing.Color.Brown),
                                     BackgroundColor = new BaseColor(System.Drawing.Color.Yellow)
                                 };
                             }
                             return new CellBasicProperties { PdfFontStyle = DocumentFontStyle.Normal };
                         });
                     });
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("مبلغ");
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(3);
                     column.Width(2);
                     column.HeaderCell("مبلغ");
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                 });
             })
             .Export(export =>
                 {
                     export.ToXml();
                     export.ToExcel();
                 })
             .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptDataTableSample.pdf"));
        }
    }
}
به همراه قالب سلول سفارشی آن:
using System;
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.CustomCellTemplate
{
    public class MyCustomCellTemplate : IColumnItemsTemplate
    {
        Random _rnd = new Random();

        public void CellRendered(PdfPCell cell, Rectangle position, PdfContentByte[] canvases, CellAttributes attributes)
        {
        }

        public CellBasicProperties BasicProperties { set; get; }
        public Func<IList<CellData>, CellBasicProperties> ConditionalFormatFormula { set; get; }

        public PdfPCell RenderingCell(CellAttributes attributes)
        {
            var pdfCell = new PdfPCell();
            var table = new PdfPTable(1) { RunDirection = PdfWriter.RUN_DIRECTION_RTL };

            var filePath = AppPath.ApplicationPath + "\\Images\\" + _rnd.Next(1, 5).ToString("00") + ".png";
            var photo = PdfImageHelper.GetITextSharpImageFromImageFile(filePath);
            table.AddCell(new PdfPCell(photo, fit: false)
            {
                Border = 0,                
                VerticalAlignment = Element.ALIGN_BOTTOM,
                HorizontalAlignment = Element.ALIGN_CENTER
            });

            var name = attributes.RowData.TableRowData.GetSafeStringValueOf("شخص");
            table.AddCell(new PdfPCell(attributes.BasicProperties.PdfFont.FontSelector.Process(name))
            {
                Border = 0,
                HorizontalAlignment = Element.ALIGN_CENTER
            });

            pdfCell.AddElement(table);

            return pdfCell;
        }
    }
}

توضیحات:

- در این مثال از منبع داده‌ای از نوع DataTable استفاده شده است؛ که نحوه بکارگیری آن‌را در متد MainTableDataSource ملاحظه می‌کنید. ستون‌های تعریف شده در MainTableColumns نیز بر اساس ستون‌های DataTable مشخص شده‌اند.
- در متد DocumentPreferences، نحوه مشخص سازی فشرده سازی نهایی فایل PDF را ملاحظه می‌کنید. این مورد از مزایای استفاده از فایل‌های PDF است.

- برای اعمال فرمت شرطی اطلاعات در PdfReport دو روش وجود دارد.
الف) استفاده از متد MainTableEvents و کار کردن با رخ‌دادهای تعریف شده در آن مانند CellCreated. در اینجا می‌توان در نحوه رندر شدن یک سلول دخالت کرد:
events.CellCreated(args =>
    {
         //change the background color of the cell based on the value
         if (args.RowType == RowType.DataTableRow && args.Cell.RowData.Value != null && args.Cell.RowData.Value is decimal)
         {
              if ((decimal)args.Cell.RowData.Value <= 1000)
                   args.Cell.BasicProperties.BackgroundColor = BaseColor.CYAN;
          }
      });
برای مثال در تعاریف فوق، اگر نوع ردیف، از نوع ردیف‌های اطلاعاتی جدول باشد، مقدار آن دریافت شده و بر اساس شرطی مشخص، برای نمونه رنگ پس زمینه آن سلول تغییر داده می‌شود.
ب) همانطور که در قسمت تعریف ستون «ماه» ملاحظه می‌کنید، توسط متد template.ConditionalFormatFormula نیز، امکان فرمت شرطی اطلاعات فراهم شده است. در اینجا می‌توان به لیست اطلاعات سلول‌های ردیف جاری دسترسی یافت و سپس بر اساس آن تصمیم گیری کرد.

- جهت تعریف قالب‌های سفارشی سلول‌ها کافی است اینترفیس IColumnItemsTemplate را پیاده سازی کنیم؛ که نمونه‌ای از آن را در کدهای MyCustomCellTemplate فوق ملاحظه می‌کنید. در اینجا فرصت خواهید داشت هر شکل و طرح متنوعی را تهیه کرده و به صورت یک PdfPCell بازگشت دهید. برای نمونه در مثال فوق، یک جدول را در سلول تعریف شده قرار داده‌ایم. این جدول یک ستون دارد و هر سلولی که به آن اضافه خواهد شد، یک ردیف را تشکیل خواهد داد. در ردیف اول آن تصویر قرار گرفته و در ردیف دوم آن مقدار سلول جاری.
اشتراک‌ها
کتابخانه مفید برای زمانبندی و صف در Asp.net Core
services.AddScheduler(scheduler =>
    {
        scheduler.Schedule(
            () => Console.WriteLine("Run at 1pm utc during week days.")
        )
        .DailyAt(13, 00);
        .Weekday();
    }
);
IQueue _queue;

public HomeController(IQueue queue) {
    this._queue = queue;
}

//... Further down ...

public IActionResult QueueTask() {
    // Call .QueueTask() to add item to the queue!
    this._queue.QueueTask(() => Console.WriteLine("This was queued!"));
    return Ok();
}
کتابخانه مفید برای زمانبندی و صف در Asp.net Core
نظرات مطالب
BloggerToCHM
در win7 x64 این خطا را می دهد: (نسخه1.4 برنامه)


Date: 1389/07/07 - 03:52:50 - MSG: System.BadImageFormatException: Could not load file or assembly 'System.Data.SQLite, Version=1.0.64.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139' or one of its dependencies. An attempt was made to load a program with an incorrect format.
File name: 'System.Data.SQLite, Version=1.0.64.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139'
at  . (String )
at  . ()
at System.Windows.Forms.Form.OnLoad(EventArgs e)
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl()
at System.Windows.Forms.Control.WmShowWindow(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

------------------------------------
Date: 1389/07/07 - 03:52:53 - MSG: System.BadImageFormatException: Could not load file or assembly 'System.Data.SQLite, Version=1.0.64.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139' or one of its dependencies. An attempt was made to load a program with an incorrect format.
File name: 'System.Data.SQLite, Version=1.0.64.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139'
at  . ()
at  . ()
at  . (Object , EventArgs )
at System.Windows.Forms.Form.OnLoad(EventArgs e)
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl()
at System.Windows.Forms.Control.WmShowWindow(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

------------------------------------
Date: 1389/07/07 - 03:53:00 - MSG: System.BadImageFormatException: Could not load file or assembly 'System.Data.SQLite, Version=1.0.64.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139' or one of its dependencies. An attempt was made to load a program with an incorrect format.
File name: 'System.Data.SQLite, Version=1.0.64.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139'
at  . (String )
at  . ()

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

------------------------------------
مطالب
MIME Sniffing و استاندارد OWASP

 یکی از نکات امنیتی که استاندارد  Owasp  بررسی مینماید هدر X-Content-Type-Options است که جهت جلوگیری از حملات از طریق فایل‌های نامرتبط میباشد. در این رخنه ممکن است فایلی که مرورگر دریافت میکند با آنچه که وب سایت ما آن را میشناسد متفاوت باشد. به عنوان مثال یک فایل اسکریپت که به عنوان یک فایل استایل معرفی میگردد ولی  قابلیت اجرای کدهای آن در مرورگر امکان پذیر است؛ به این نوع حملات MIME Sniffing میگویند. در یکی از سایت‌هایی که در حال حاضر در حال توسعه آن هستیم بخشی از گزارش‌ها با استفاده از ابزار FastReport  ایجاد شده بود و با توجه به اینکه در این ابزار از فایل‌های axd استفاده می‌گردید و مرورگر نوع دیتای برگشتی و MimeType معرفی شده را همخوان نمیدانست، از نمایش و بارگذاری آن ممانعت به عمل آورده و در نتیجه فایل گزارش دیده نمیشد. در این مقاله قصد داریم به معرفی این نوع حمله، روش جلوگیری از آن و همچنین رفع محدودیت پیش آمده را بررسی کرده تا در موارد مشابه از آن استفاده نماییم.


Mime Sniffing

زمانیکه مرورگر در هدر(پاسخ) Response، نوع محتوای ارسال شده، Content-Type را دریافت نکند، یا مرورگر متوجه مغایرتی در آن شود، این نوع رفتار را Mime Sniffing شناسایی میکند. نحوه این شناسایی‌ها در هر مرورگری میتواند متفاوت باشد؛ ولی عموما بر اساس type ارسالی و پسوند مورد نظر میباشد. در بعضی از موارد نیز خواندن بایت‌های ابتدایی یک فایل نیز میتواند نشان دهد که محتوای ارسالی واقعا چیست. به عنوان مثال برای فایل‌هایی با پسوند Gif، الگوی بایت‌های ابتدایی شامل  47 49 46 38 39 میباشد؛ ولی از آنجا که در همه فایل‌ها، بایت‌های ابتدایی الگوی یکسانی ندارند، پس نمی‌توان به این روش نیز بسنده کرد.


روش اینکه به مروگر بگوییم جلوی این نوع حملات را بگیرد و در صورت شناسایی Sniffing از اجرای آن سر باز بزند، استفاده از هدر X-Content-Type-Options میباشد که نحوه افزودن آن در فایل web.config  به شکل زیر است:

 <httpProtocol>
      <customHeaders>
  ...
        <remove name="X-Content-Type-Options"/>
        <add name="X-Content-Type-Options" value="nosniff" />
        ..
      </customHeaders>
    </httpProtocol>

در پروژه ما به دلیل اینکه بخشی از گزارش‌ها با استفاده از FastReport طراحی شده بود این مورد برای ما ایجاد مشکل می‌کرد و در گزارش نمایش داده نمی‌شد و در کنسول پیام‌هایی به شکل زیر دریافت میکردیم:

Refused to execute script from 'https://localhost:44377/WebResource.axd?d=xxx' because its MIME type ('text/js') is not executable, and strict MIME type checking is enabled.

با نگاهی به Response  دریافتی نیز میتوان بازگشت هدر امنیتی X-Content-Type-Options را نیز مشاهده نمود:

 پس باید این هدر را برای بعضی از آدرس‌ها که میتوانند دچار مشکلات اجرایی گردند حذف کرده و برای مابقی بخش‌ها همچنان این هدر فعال باشد؛ پس با افزودن کد زیر به web.config، هدر مورد نظر را برای این نوع فایل حذف میکنیم:

  <location path="WebResource.axd" >
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <remove name="X-Content-Type-Options" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
  </location>

 نتیجه آن را میتوانید در صفحه ذیل برای همان درخواست و پاسخ قبلی نیز مشاهده نمایید:

 
مطالب
ایجاد چارت سازمانی تحت وب #4 - آخر
نما : Layout
در یک نمودار یا چارت سازمانی در حد امکان شاخه‌ها همواره در کنار هم و جمع و جور رسم میشوند. در مثال زیر نود u-Node 1 و u-Node 3 دارای زیر شاخه نبوده ، بنابراین نیازی به فضای زیرین جهت نمایش ندارند. جهت مشاهده این فضا میتوانید خط مشخص شده در کد را فعال نمائید و تفاوت فضای مورد نیاز و ایجاد شده را ببینید.
دو درخت نمودار متفاوت در کنار هم رسم شده اند. هیچ همپوشانی بین درختان رسم شده وجود ندارد ( بنابراین نود Root 2 روی نود u-Node 3  رسم نشده است.


var o = new orgChart();

o.addNode( 0, '', '', 'Root 1');
o.addNode( 1,  0, 'u', 'u-Node 1');
o.addNode( 2,  0, 'u', 'u-Node 2');
o.addNode( 3,  0, 'u', 'u-Node 3');
o.addNode( 4,  0, 'u', 'u-Node 4');
//میتوانید خط زیر را فعال نمائید تا تفاوت فضای ایجاد شده و مورد نیاز را مشاهده نمائید.
 //o.addNode( 9, 3, 'u', 'EXTRA', 0, '', '#CC0000', '#FF0000'); o.addNode(11, 2, 'l', 'l-Node'); o.addNode(12, 2, 'u', 'u-Node'); o.addNode(13, 2, 'u', 'u-Node'); o.addNode(14, 2, 'r', 'r-Node'); o.addNode(20, 4, 'l', 'l-Node'); o.addNode(21, '', '', 'Root 2'); o.drawChart('c_layout');
یک مثال کامل :
نمونه زیر یک مثال کامل میباشد. انواع اتصالهای تو در تو چندگانه در این نمونه استفاده شده است .


این هم کد نمونه فوق :

var o = new orgChart();

o.setFont('Arial', 18);
o.addNode( 0, '', '', 'President', 1);

o.setFont('Arial', 12);
o.addNode('',  0, 'l', 'Control');
o.addNode('',  0, 'l', 'Secretary');
o.addNode('',  0, 'l', 'Marketing');
o.addNode('',  0, 'l', 'Human resources');

o.setColor('#99CC99', '#CCFFCC');
o.addNode(12,  0, 'r', 'Facility');
o.addNode('', 12, 'r', 'IT');
o.addNode('', 12, 'r', 'Resource planning');

o.setFont('Arial', 18);
o.setColor('#CCCC66', '#FFFF99');
o.addNode(20,  0, 'u', 'Projects', 1);

o.setFont('Arial', 12);
o.addNode('', 20, 'r', 'Management');
o.addNode('', 20, 'r', 'Finance');
o.addNode('', 20, 'l', 'Development');
o.addNode('', 20, 'l', 'Maintenance');
o.addNode('', 20, 'l', 'Specials');

o.setColor('#CC4950', '#FF7C80');
o.setFont('Arial', 18);
o.addNode(30,  0, 'u', 'Specials', 1);

o.setFont('Arial', 12);
o.addNode(31, 30, 'l', 'Management');
o.addNode('', 30, 'l', 'Communication');
o.addNode(33, 30, 'r', 'Development');
o.addNode(34, 33, 'r', 'Maintenance');
o.addNode('', 33, 'r', 'Special A');
o.addNode('', 33, 'r', 'Special B');
o.addNode('', 33, 'r', 'Advice');
o.addNode('', 30, 'l', 'Taskforce');

o.setColor('#CC9966', '#FFCC99');
o.setFont('Arial', 18);
o.addNode(40,  0, 'u', 'Programming', 1);

o.setFont('Arial', 12);
o.addNode(41, 40, 'l', 'Management');
o.addNode(42, 40, 'l', 'Finance');
o.addNode('', 40, 'r', 'Assessment');
o.addNode('', 40, 'r', 'Coding team');
o.addNode('', 40, 'r', 'Quality control');

o.drawChart('c_ex1', '', true);
اضافه کردن تصویر به نودها :
شما میتوانید به نودها تصویر دلخواه خود را نیز اضافه نمائید. تصاویر بصورت عمودی قرار خواهند گرفت و در صورتی که بزرگ باشند تغییر اندازه خواهند داد. ( فراخوانی تابع setSize قبل از اضافه کردن عکس در این مثال )

کدهای مثال فوق :

var o = new orgChart();

o.setSize(120, 60);

o.setFont('Arial', 18);
o.addNode( 1, '', '', 'Icon smiley', 0, '', '', '', '', 'pic/smiley.gif');
o.addNode( 2, '', '', 'This is a tree', 0, '', '', '', '', 'pic/tree.jpg');
o.addNode( 3, 2, 'u', 'This is a tree');
o.addNode( 4, '', '', 'Right Top smiley', 0, '', '', '', '', 'pic/smiley.gif', 'rt');
o.addNode( 5, '', '', 'Center bottom smiley', 0, '', '', '', '', 'pic/smiley.gif', 'cb');

o.drawChart('c_img');

یک مثال دیگر از استفاده تصاویر در چارت :


var o = new orgChart();

o.setSize(60, 110);

o.setFont('Arial', 12);
o.addNode( 1, '', '', 'Hominidae');
o.addNode( 10, 1, 'l', 'Hominidae');
o.addNode( 11, 10, 'l', 'Hominini');
o.addNode( 12, 10, 'r', 'Gorillini');
o.addNode( 20, 1, 'r', 'Ponginae');
o.addNode( '', 11, '', 'Homo Sapiens', '', '', '', '', '', 'pic/homo.jpg', 'ct');
o.addNode( '', 11, '', 'Pan', '', '', '', '', '', 'pic/pan.jpg', 'ct');
o.addNode( '', 12, '', 'Gorilla', '', '', '', '', '', 'pic/gorilla.jpg', 'ct');
o.addNode( '', 20, '', 'Pongo', '', '', '', '', '', 'pic/pongo.jpg', 'ct');

o.drawChart('c_img2', 'c');
تبدیل تصویر از فرمت jpg به تصویر با فرمت png :
عدم امکان استفاده از مرورگر IE تا نسخه 8 ، چرا که IE  هیچ پشتیبانی از toDataURL در excanvas.js  را انجام نمیدهد.
شما میتوانید از توابع استاندارد canvas در جهت تبدیل محتویات canvas به تصویر استاتیک استفاده نمائید. برای اینکه بتوانید این کار را تست نمائید باید کد ذیل را در همان صفحه ای که کد ( یک مثال کامل ) را استفاده کردید درج نمائید و در این صورت با کلیک بر روی لینک اول میتوانید یک تصویر با فرمت png در یک صفحه جدید از نمودار خود بدست بیاورید و یا با کلیک بر روی لینک دوم یک تصویر را دانلود نمائید.
<script type="text/javascript">
function openAsPng(id){
        window.open(document.getElementById(id).toDataURL("image/png"));
}
function saveAsPng(id){
var img = document.getElementById(id).toDataURL("image/png");
        document.location.href = img.replace("image/png", "image/octet-stream");
}
</script>

<a href = "javascript:openAsPng('c_ex1');">Click here to open the image as png in a new window</a><BR>
<a href = "javascript:saveAsPng('c_ex1');">Click here to save the image as png</a><BR>

نمایش چارت فقط بصورت یک تصویر :

برای اینکه بتوانید یک چارت ایجاد شده از این روش را فقط بصورت یک تصویر نمایش دهید باید عمل تبدیل به عکس را بلافاصله پس از رسم نمودار در canvas انجام دهید بدین صورت که در کد ذیل مشاهده مینمائید:
<canvas id="c_pngchart" width="1" height="1">Your browser does not support canvas!</canvas>

<img id="pngchart">
<script type="text/javascript">
var o = new orgChart();
o.addNode(0, '', '', 'Root');
o.addNode(1, 0, 'u', 'u-Node 1');
o.addNode(2, 0, 'u', 'u-Node 2');
o.drawChart('c_pngchart', '', true);
var canvas = document.getElementById('c_pngchart');
document.getElementById("pngchart").src = canvas.toDataURL("image/png");
// The html keyword "hidden" doesn't work in IE, so resize the canvas to NUL
canvas.height = 0;
canvas.width = 0;
</script>

همه لینکهایی که در نودها ایجاد شده است غیرفعال شده و از کار می‌افتند. بنابراین برای انجام این کار ،یک المنت تصویر در صفحه خود ایجاد ، نمودار را در canvas رسم نموده ، نمودار را به تصویر تبدیل نموده و آن را به المنت تصویر مقید میکنیم و در آخر canvas مخفی میکنیم. برای این منظور از کلید واژه hidden  استفاده میکنیم که در IE این کلمه باز قابل شناسایی نبوده و باید از روش تخصیص اندازه طول و عرض صفر 0 استفاده شود یعنی width=0 , height=0

تصویر فوق ایجاد شده کد مورد نظر میباشد.

تغییر اندازه پویا :
اگر بخواهید بصورت پویا اندازه canvas را تغییر دهید ، نمودار شما ناپدید میشود و پس از تغییر اندازه ، نمودار پاک خواهد شد.
برای رسم نمودار باید دوباره از توابع drawChart() یا redrawChart()   استفاده نمائید.
برای رسم نودها نیازی به تعریف دوباره آنها نمیباشد ( مخصوصا در مثالی که در این صفحه برای شما ارائه شده است )
تابع ()drowChart تمامی نودها را در زمان رسم دوباره جاگذاری میکند ، در صورتی که اگر شما میدانید چارت شما به غیر از اندازه هیچ تغییر دیگری نداشته میتوانید با فراخوانی تابع redrawChart  یک کپی از همان چارت را که در حافظه canvas وجود دارد را رسم نمائید.
از تمامی دوستان خوبم تشکر میکنم که این مطلب را دنبال نمودند . ما را از نظرات خوب و سازنده خود بی نصیب نفرمائید.

مطالب
Query Options در پروتکل OData
در قسمت قبل  با OData به صورت مختصر آشنا شدیم. در این قسمت به امکانات توکار OData و جزئیات query options پرداخته و همچنین قابلیت‌های امنیتی این پروتکل را بررسی مینماییم.
در قسمت قبلی، config مربوط به OData و همچنین Controller و Crud مربوط به آن entity پیاده سازی شد. در این قسمت ابتدا سه موجودیت را به نام‌های Product ، Category و همچنین Supplier، به صورت زیر تعریف مینماییم:

به این صورت مدل‌های خود را تعریف کرده و طبق مقاله‌ی قبلی، Controller‌های هر یک را پیاده سازی نمایید:

public class Supplier
{
    [Key]
    public string Key {get; set; }
    public string Name { get; set; }
}
public class Category
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [ForeignKey("Category")]
    public int CategoryId { get; set; }
    public Category Category { get; set; }

    [ForeignKey("Supplier")]
    public string SupplierId { get; set; }
    public virtual Supplier Supplier { get; set; }
}

پکیج Microsoft.AspNet.OData به تازگی ورژن 6 آن به صورت رسمی منتشر شده و شامل تغییراتی نسبت به نسخه‌ی قبلی آن است. اولین نکته‌ی حائز اهمیت، Config آن است که به صورت زیر تغییر کرده و باید Option‌های مورد نیاز، کانفیگ شوند. در این نسخه DI نیز به Odata اضافه شده است:

public static void Register(HttpConfiguration config)
        {
            ODataModelBuilder odataModelBuilder = new ODataConventionModelBuilder();

            var product = odataModelBuilder.EntitySet<Product>("Products");
            var category = odataModelBuilder.EntitySet<Category>("Categories");
            var supplier = odataModelBuilder.EntitySet<Supplier>("Suppliers");

            var edmModel = odataModelBuilder.GetEdmModel();

            supplier.EntityType.Ignore(c => c.Name);

            config.Select(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.MaxTop(25);
            config.OrderBy(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Expand(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Filter(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed);

            //config.MapODataServiceRoute("ODataRoute", "odata", edmModel); // کانفیگ به صورت معمولی
           config.MapODataServiceRoute("ODataRoute", "odata",
                builder =>
                {
                    builder.AddService(ServiceLifetime.Singleton, sp => edmModel);
                    builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault());
                });
}
باید همه‌ی Query option‌هایی را که به آنها نیاز داریم، معرفی نماییم و فرض کنید که ProductsController و متد Get آن بدین صورت پیاده سازی شده باشد:
[EnableQuery]
        public IQueryable<Product> Get()
        {
            return new List<Product>
            {
                new Product { Id = 1, Name = "name 1", Price = 11, Category = new Category {Id =1, Name = "Cat1" } },
                new Product { Id = 2, Name = "name 2", Price = 12, Category = new Category {Id =2, Name = "Cat2" } },
                new Product { Id = 3, Name = "name 3", Price = 13, Category = new Category {Id =3, Name = "Cat3" } },
                new Product { Id = 4, Name = "name 4", Price = 14, Category = new Category {Id =4, Name = "Cat4" } },
            }.AsQueryable(); ;
        }
در این پروتکل به صورت توکار، optionهای زیر قابل استفاده است:

 Description Option 
 بسط دادن موجودیت مرتبط  $expand
 فیلتر کردن نتیجه، بر اساس شرط‌های Boolean ی  $filter
 فرمان به سرور که تعداد رکورد‌های بازگشتی را نیز نمایش دهد(مناسب برای پیاده سازی server-side pagging )  $count
 مرتب کردن نتیجه‌ی بازگشتی  $orderby
 select زدن روی پراپرتی‌های درخواستی  $select
 پرش کردن از اولین رکورد به اندازه‌ی n عدد  $skip
 فقط بازگرداندن n رکورد اول  $top
کوئری‌های زیر را در نظر بگیرید:

 در کوئری اول، فقط فیلد‌های Id,Name از Products برگشت داده خواهند شد و در کوئری دوم، از 2 رکورد اول، صرفنظر می‌شود و از بقیه‌ی آنها، فقط 3 رکورد بازگشت داده میشود:
/odata/Products?$select=Id,Name
/odata/Products?$top=3&$skip=2
/odata/Products?$count=true
در پاسخ کوئری فوق، تعداد رکورد‌های بازگشتی نیز نمایش داده میشوند:
{@odata.context: "http://localhost:4516/odata/$metadata#Products", @odata.count: 4,…}
@odata.context:"http://localhost:4516/odata/$metadata#Products"
@odata.count:4
value:[{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0},…]
0:{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0}
1:{Id: 2, Name: "name 2", Price: 12, SupplierId: 0, CategoryId: 0}
2:{Id: 3, Name: "name 3", Price: 13, SupplierId: 0, CategoryId: 0}
3:{Id: 4, Name: "name 4", Price: 14, SupplierId: 0, CategoryId: 0}
در response، این مقادیر به همراه تعداد رکورد بازگشتی، نمایش داده میشوند که برای پیاده سازی paging مناسب است.
/odata/Products?$filter=Id eq 1
در کوئری فوق eq مخفف equal و به معنای برابر است و بجای آن میتوان از gt به معنای بزرگتر و lt به معنای کوچکتر نیز استفاده کرد:
/odata/Products?$filter=Id gt 1 and Id lt 3
/odata/Products?$orderby=Id desc
در کوئری فوق نیز به صورت واضح، بر روی فیلد Id، مرتب سازی به صورت نزولی خواهد بود و در صورت وجود نداشتن کلمه کلیدی desc، به صورت صعودی خواهد بود.
بسط دادن موجودیت‌های دیگر نیز بدین شکل زیر میباشد:
/odata/Products?$expand=Category
برای اینکه چندین موجودیت دیگر نیز بسط داده شوند، اینگونه رفتار مینماییم:
/odata/Products?$expand=Category,Supplier
برای اینکه به صورت عمیق به موجودیت‌های دیگر بسط داده شود، بصورت زیر:
/odata/Categories(1)?$expand=Products/Supplier
و برای اینکه حداکثر تعداد رکورد بازگشتی را مشخص نماییم:
[EnableQuery(PageSize = 10)]


محدود کردن Query Options
به صورت زیر میتوانیم فقط option‌های دلخواه را فراخوانی نماییم. مثلا در اینجا فقط اجازه‌ی Skip و Top داده شده است و بطور مثال Select قابل فراخوانی نیست:
[EnableQuery (AllowedQueryOptions= AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]
برای اینکه فقط اجازه‌ی logical function زیر را بدهیم (فقط eq):
[EnableQuery(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]
برای اینکه Property خاصی در edm قابلیت نمایش نباشد، در config بطور مثال Price را ignore مینماییم:
var product = odataModelBuilder.EntitySet<Product>("Products");
product.EntityType.Ignore(e => e.Price);
برای اینکه فقط بر روی فیلد‌های خاصی بتوان از orderby استفاده نمود:
[EnableQuery(AllowedOrderByProperties = "Id,Name")]
یک option به نام value$ برای بازگرداندن تنها آن رکورد مورد نظر، به صورت مجزا میباشد. برای اینکار بطور مثال متد زیر را به کنترلر خود اضافه کنید:
public System.Web.Http.IHttpActionResult GetName(int key)
        {
            Product product = Get().Single(c => c.Id == key);            
            return Ok(product.Name);
        }
/odata/Products(1)/Name/$value
و حاصل کوئری فوق، مقداری بطور مثال برابر زیر خواهد بود و نه به صورت convention پاسخ‌های OData، فرمت بازگشتی "text/plain" خواهد بود و نه json:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 3

Ali

Attribute Convention
هایی هم برای اعتبارسنجی پراپرتی‌ها موجود است که نام آن‌ها واضح تعریف شده‌اند:
 Description  Attribute
 اجازه‌ی فیلتر زدن بر روی آن پراپرتی داده نخواهد شد  NotFilterable
 اجازه‌ی مرتب کردن بر روی آن پراپرتی داده نخواهد شد  NotSortable
 اجازه‌ی select زدن بر روی آن پراپرتی داده نمیشود  NotNavigable
 اجازه‌ی شمارش دهی بر روی آن Collection داده نمیشود  NotCountable
 اجازه‌ی بسط دادن آن Collection داده نمیشود  NotExpandable

و همچنین [AutoExpand] به صورت اتوماتیک آن موجودیت مورد نظر را بسط میدهد.

بطور مثال کد‌های زیر را در مدل خود میتوانید مشاهده نمائید:

public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        [NotFilterable, NotSortable]
        public decimal Price { get; set; }

        [ForeignKey(nameof(SupplierId))]
        [NotNavigable]
        public virtual Supplier Supplier { get; set; }
        public int SupplierId { get; set; }


        [ForeignKey(nameof(CategoryId))]
        public virtual Category Category { get; set; }
        public int CategoryId { get; set; }

        [NotExpandable]
        public virtual ICollection<TestEntity> TestEnities { get; set; }
    }

فرض کنید پراپرتی زیر را به مدل خود اضافه کرده اید

public DateTimeOffset CreatedOn { get; set; }

حال کوئری زیر را برای فیلتر زدن، بر روی آن در اختیار داریم:

/odata/Products?$filter=year(CreatedOn) eq 2016

در اینجا فقط Product هایی بازگردانده میشوند که در سال 2016 ثبت شده‌اند:

/odata/Products?$filter=CreatedOn lt cast(2017-04-01T04:11:31%2B08:00,Edm.DateTimeOffset)

کوئری فوق تاریخ مورد نظر را Cast کرده و همه‌ی Product هایی را که قبل از این تاریخ ثبت شده‌اند، باز می‌گرداند.

Nested Filter In Expand

/odata/Categories?$expand=Products($filter=Id gt 1 and Id lt 5)

همه‌ی Category‌ها به علاوه بسط دادن Product هایشان، در صورتیکه Id آنها بیشتر از 1 باشد

و یا حتی بر روی موجودیت بسط داده شده، select زده شود:

/odata/Categories?$expand=Products($select=Id,Name)


Custom Attribute

ضمنا به سادگی میتوان اتریبیوت سفارشی نوشت:

public class MyEnableQueryAttribute : EnableQueryAttribute
{
    public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
    {
       // Don't apply Skip and Top.
       var ignoreQueryOptions = AllowedQueryOptions.Skip | AllowedQueryOptions.Top;
       return queryOptions.ApplyTo(queryable, ignoreQueryOptions);
    }
}

روی هر متدی از کنترلر خود که اتریبیوت [MyEnableQuery] را قرار دهید، دیگر قابلیت Skip, Top را نخواهد داشت.

Dependency Injection در آخرین نسخه‌ی OData اضافه شده است. بطور پیشفرض OData بصورت case-sensitive رفتار میکند. برای تغییر دادن آن در نسخه‌های قدیمی، Extension Methodی به نام EnableCaseSensitive وجود داشت. اما در نسخه‌ی جدید شما میتوانید پیاده سازی خاص خود را از هر کدام از بخش‌های OData داشته باشید و با استفاده از تزریق وابستگی، آن را به config برنامه‌ی خود اضافه کنید؛ برای مثال:

 public class CaseInsensitiveResolver : ODataUriResolver
    {
        private bool _enableCaseInsensitive;

        public override bool EnableCaseInsensitive
        {
            get { return true; }
            set { _enableCaseInsensitive = value; }
        }
    }

اینجا پیاده سازی از ODataUriResolver انجام شده و متد EnableCaseInsensitive به صورت جدیدی override و در حالت default مقدار true را برمیگرداند.

حال به صورت زیر آن را می‌توان به وابستگی‌های config برنامه، اضافه نمود:

config.MapODataServiceRoute("ODataRoute", "odata",
                builder =>
                {
                    builder.AddService(ServiceLifetime.Singleton, sp => edmModel);
                    builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault());
                    builder.AddService<ODataUriResolver>(ServiceLifetime.Singleton, sp => new CaseInsensitiveResolver()); // how enable case sensitive
                });

در قسمت بعدی به Action‌ها و Function‌ها در OData میپردازیم.