مطالب
باگ امنیتی در ASP.NET و نحوه‌ی رفع آن

بعد از مدت‌ها بالاخره یک باگ امنیتی در ASP.NET کشف شده! البته این "بعد از مدت‌هایی" که عنوان شد بر اساس آمار است که در سایت بی‌طرف Secunia قابل بررسی و مشاهده است (و اگر از یک سری کامپوننت و برنامه‌های جانبی سایر برنامه نویس‌ها صرفنظر کنیم، انصافا ضریب امنیتی بالایی را ارائه داده):

البته این مورد جدید هم مرتبط با خود ASP.NET نیست؛ بلکه مرتبط است با نحوه‌ی پیاده سازی الگوریتم AES در دات نت فریم ورک (Padding Oracle / AES cookie encryption vulnerability).
توضیحات بیشتر در سایت Scottgu


خلاصه این توضیحات:
همانطور که هزاران بار به برنامه نویسان ASP.NET (و SharePoint) گوشزد شده است، لطفا صفحه‌ی نمایش خطاهای سفارشی سایت را (customErrors) در web.config برنامه on کنید. این خطای امنیتی موجود در پیاده سازی الگوریتم AES نیاز به سعی و خطاهای زیاد روی سایت و بررسی پاسخ‌های داده شده یا ریزخطاهای منتشر شده دارد. اگر صفحه‌ی خطای سفارشی سایت شما تنظیم شده و روشن است، خطری سایت شما را تهدید نمی‌کند؛ زیرا شخص مهاجم هیچ خروجی خطایی را جهت بررسی مشاهده نخواهد کرد.


مطالب
کار با دیتاتایپ 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]"

مطالب
ASP.NET MVC #1

چرا ASP.NET MVC ؟

با وجود فریم ورک پخته‌ای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح می‌شود این است: «برای چی؟». بنابراین تا به این سؤال پاسخ داده نشود، هر نوع بحث فنی در این مورد بی فایده است.

مزایای ASP.NET MVC نسبت به ASP.NET web forms

1) سادگی نوشتن آزمون‌های واحد
مهم‌ترین دلیل استفاده از ASP.NET MVC صرفنظر از تمام دلایل دیگر، بحث طراحی ویژه آن جهت ساده سازی تهیه آزمون‌های واحد است. مشکل اصلی نوشتن آزمون‌های واحد برای برنامه‌های ASP.NET web forms، درگیر شدن مستقیم با تمام جزئیات طول عمر یک صفحه است. به علاوه فایل‌های code behind هر چند به ظاهر کدهای منطق یک صفحه را از کدهای HTML مانند آن جدا می‌کنند اما در عمل حاوی ارجاعات مستقیمی به تک تک عناصر بصری موجود در صفحه هستند (حس غلط جدا سازی کدها از اجزای یک فرم). اگر قرار باشد برای این وب فرم‌ها و صفحات، آزمون واحد بنویسیم باید علاوه بر شبیه سازی چرخه طول عمر صفحه و همچنین رخدادهای رسیده، کار وهله سازی تک تک عناصر بصری را نیز عهده دار شویم. اینجا است که ASP.NET web forms گزینه‌ی مطلوبی برای این منظور نخواهد بود و اگر نوشتن آزمون واحد برای آن غیرممکن نباشد، به همین دلایل آنچنان مرسوم هم نیست.
البته شاید بپرسید که این مساله چه اهمیتی دارد؟ امکان نوشتن ساده‌تر آزمون‌های واحد مساوی است با امکان ساده‌تر اعمال تغییرات به یک پروژه بزرگ. تغییرات در پروژه‌های بزرگی که آزمون واحد ندارند واقعا مشکل است. یک قسمت را تغییر می‌دهید، 10 قسمت دیگر به هم می‌ریزند. اینجا است که مدام باید به کارفرما گفت: «نه!»، «نمیشه!» یا به عبارتی «نمی‌تونم پروژه رو جمع کنم!» چون نمی‌تونم سریع برآورد کنم که این تغییرات کدام قسمت‌ها را تحت تاثیر قرار می‌دهند، کجا به هم ریخت. من باید خودم سریع بتونم مشخص کنم با این تغییر جدید چه قسمت‌هایی به هم ریخته تا اینکه دو روز بعد زنگ بزنند: «باز جایی رو تغییر دادی، یکجای دیگر کار نمی‌کنه!»

2) دستیابی به کنترل بیشتر بر روی اجزای فریم ورک
در طراحی ASP.NET MVC همه‌جا interface ها قابل مشاهد هستند. همین مساله به معنای افزونه پذیری اکثر قطعات تشکیل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. برای مثال تابحال چندین view engine، routing engine و غیره توسط برنامه نویس‌های مستقل برای ASP.NET MVC طراحی شده‌اند که هیچکدام با ASP.NET web forms میسر نیست. برای مثال از view engine پیش فرض آن خوشتان نمی‌آید؟ عوضش کنید! سیستم اعتبار سنجی توکار آن‌را دوست ندارید؟ آن‌را با یک نمونه بهتر تعویض کنید و الی آخر ...
به علاوه طراحی بر اساس interface ها یک مزیت دیگر را هم به همراه دارد و آن هم ساده سازی mocking (تقلید) آن‌ها است جهت ساده سازی نوشتن آزمون‌های واحد.

3) سرعت بیشتر اجرا
ASP.NET MVC یک سری از قابلیت‌های ذاتی ASP.NET web forms را مانند ViewState حذف کرده است. اگر وب را جستجو کنید، برنامه نویس‌های ASP.NET web forms مدام از این مساله شکایت دارند و راه‌ حل‌های مختلفی را جهت حذف یا فشرده سازی آن ارائه می‌دهند. ViewState در ابتدای امر جهت شبیه سازی محیط دسکتاپ در وب درنظر گرفته شده بود و مهاجرت ساده‌تر برنامه نویس‌های VB6 به وب، اما واقعیت این است که اگر یک برنامه نویس ASP.NET web forms به اندازه آن توجهی نداشته باشد، ممکن است حجم آن در یک صفحه پیچیده تا 500 کیلوبایت یا بیشتر هم برسد. همین مساله بر روی سرعت دریافت و اجرا تاثیر گذار خواهد بود.

4) کنترل‌های ASP.NET web forms آنچنان آش دهن‌سوزی هم نیستند!
خوب، ViewState حذف شده، بنابراین اکثر کنترل‌های ASP.NET web forms هم کاربرد آنچنانی در ASP.NET MVC نخواهند داشت؛ اما واقعیت این است که اکثر اوقات اگر شروع به سفارشی سازی یک کنترل توکار ASP.NET web forms کنید تا مطابق نیازهای کاری شما رفتار کند، پس از مدتی به یک کنترل کاملا از نو بازنویسی شده خواهید رسید! بنابراین در ابتدای امر تا 80 درصد کار اینطور به نظر می‌رسد که به عجب سرعت بالایی در توسعه دست یافته‌ایم، اما هنگامیکه قرار است این 20 درصد پایانی را پر کنیم، به این نتیجه خواهیم رسید که این کنترل‌ها با این وضع ابتدایی که دارند قابل استفاده نیستند و نیاز به دستکاری قابل ملاحظه‌ای دارند تا نیازهای واقعی کاری را برآورده کنند.

5) کنترل کامل بر روی HTML نهایی تولیدی
اگر علاقمند به کار با jQuery باشید، مدام نیاز خواهید تا با ID کنترل‌ها و عناصر صفحه کار کنید. پیشتر ASP.NET web forms این ID را یک طرفه و به صورت مقدار منحصربفردی تولید می‌کرد که جهت کار با فریم ورک‌های جاوا اسکریپتی عموما مشکل ساز بود. البته ASP.NET web forms در نگارش‌های جدید خود مشکل عدم امکان مقدار دهی ClientId سفارشی را برای کنترل‌های وب خود برطرف کرده است و این مورد را می‌توان دستی هم تنظیم کرد ولی در کل باز هم آنچنان کنترلی رو خروجی HTML نهایی کنترل‌های تولیدی نیست مگر اینکه مانند مورد چهارم یاد شده یک کنترل را از صفر بازنویسی کنید!
همچنین اگر باز هم بیشتر با jQuery و ASP.NET web forms کار کرده باشید می‌دانید که jQuery آنچنان سنخیتی با ViewState و Postback وب فرم‌ها ندارد و همین مساله عموما مشکل‌زا است. علاوه بر آن اخیرا مایکروسافت توسعه ASP.NET Ajax خود را تقریبا در حالت تعلیق و واگذار شده به شرکت‌های ثالث درآورده است و توصیه آن‌ها استفاده از jQuery Ajax است. اینجا است که مدل ASP.NET MVC سازگاری کاملی را با jQuery Ajax دارد هم از لحاظ نبود ViewState و هم از جنبه‌ی کنترل کامل بر روی markup نهایی تولیدی.
یا برای مثال خروجی پیش فرض یک GridView، جدول HTML ایی است که این روزها همه‌جا علیه آن صحبت می‌شود. البته یک سری آداپتور CSS friendly برای اکثر این کنترل‌ها موجود است و ... باز هم دستکاری بیش از حد کنترل‌های پیش فرض جهت رسیدن به خروجی دلخواه. تمام این‌ها را در ASP.NET MVC می‌شود با معادل‌های بسیار باکیفیت افزونه‌های jQuery جایگزین کرد و از همه مهم‌تر چون ViewState و مفاهیمی مانند PostBack حذف شده، استفاده از این افزونه‌ها مشکل ساز نخواهد بود.

6) استفاده از امکانات جدید زبان‌های دات نتی
طراحی اصلی ASP.NET web forms مربوط است به دوران دات نت یک؛ زمانیکه نه Generics وجود داشت، نه LINQ و نه آنچنان مباحث TDD یا استفاده از ORMs متداول بود. برای مثال شاید ایجاد یک strongly typed web form الان کمی دور از ذهن به نظر برسد، زمانیکه اصل آن بر مبنای بکارگیری گسترده datatable و dataset بوده است (با توجه به امکانات زبان‌های دات نتی در آن دوران). بنابراین اگر علاقمند هستید که این امکانات جدید را بکاربگیرید، ASP.NET MVC برای استفاده از آن‌ها طراحی شده است!

7) از ASP.NET web forms ساده‌تر است
طراحی ASP.NET MVC بر اساس ایده Convention over configuration است. به این معنا که اجزای آن بر اساس یک سری قرار داد در کنار هم مشغول به کار هستند. مشخص است View باید کجا باشد، نام کنترلرها چگونه باید تعیین شوند و قرار داد مرتبط به آن چیست، مدل باید کجا قرار گیرد، قرار داد پردازش آدرس‌های صفحات سایت به چه نحوی است و الی آخر. خلاصه در بدو امر با یک فریم ورک حساب شده که شما را در مورد نحوه استفاده صحیح از آن راهنمایی می‌کند، مواجه هستید.
به همین ترتیب هر پروژه MVC دیگری را هم که مشاهده کنید، سریع می‌توانید تشخیص دهد قراردادهای بکارگرفته شده در آن چیست. بنابراین اگر قرار است ASP.NET را امروز شروع کنید و هیچ سابقه‌ای هم از وب فرم‌ها ندارید، یک راست با ASP.NET MVC شروع کنید.

8) محدود به پیاده سازی مایکروسافت نیست
پیاده سازی‌های مستقلی هم از ASP.NET MVC توسط اشخاص و گروه‌های خارج از مایکروسافت وجود دارد: ^، ^، ^، ^ و ...


و در پایان یکی دیگر از دلایل سوئیچ به ASP.NET MVC ، «یاد گرفتن یک چیز جدید است» یا به عبارتی فرا گرفتن یک روش دیگر برای حل مسایل، هیچگاه ضرری را به همراه نخواهد داشت که هیچ، بلکه باعث بازتر شدن میدان دید نیز خواهد گردید.


یک دیدگاه دیگر
ASP.NET MVC برای شما مناسب نخواهد بود اگر ...
1) با پلی‌مرفیزم مشکل دارید.
ASP.NET MVC پر است از interfaces، abstract classes، virtual methods و امثال آن. بنابراین اگر تازه کار هستید، ابتدا باید مفاهیم شیءگرایی را تکمیل کنید.

2) اگر نمی‌توانید فریم ورک خودتون رو بر پایه ASP.NET MVC بنا کنید!
ASP.NET MVC برخلاف وب فرم‌ها به همراه آنچنان تعداد بالایی کنترل و افزونه از پیش مهیا شده نیست. در بدو امر شما فقط یک سری url helper، html helper و ajax helper ساده را خواهید دید؛ این نقطه ضعف ASP.NET MVC نیست. عمدا به این نحو طراحی شده است. همانطور که عنوان شد اکثر اجزای این فریم ورک قابل تعویض است. بنابراین دست شما را باز گذاشته است تا با پیاده سازی این اینترفیس‌ها، امکانات جدیدی را خلق کنید. البته پس از این چندین و چند سال که از ارائه آن می‌گذرد، به اندازه کافی افزونه برای ASP.NET MVC طراحی شده است که به هیچ عنوان احساس کمبود نکنید یا اینکه نیازی هم نداشته باشید تا آنچنان فریم ورک خاصی را بر پایه ASP.NET MVC تهیه کنید. برای مثال پروژه MvcContrib موجود است یا شرکت telerik یک مجموعه سورس باز کامل مخصوص ASP.NET MVC را ارائه داده است و الی آخر.

3) اگر نمی‌توانید از کتابخانه‌های سورس باز استفاده کنید.
همانطور که عنوان شد ASP.NET MVC به همراه کوهی از کنترل‌ها ارائه نشده است. اکثر افزونه‌های آن سورس باز هستند و کار با آن‌ها هم دنیای خاص خودش را دارد. چگونه باید کتابخانه‌های مناسب را پیدا کرد، کجا سؤال پرسید، کجا باگ گزارش داد، چگونه مشارکت کرد و غیره. خلاصه منتظر یک بسته شکیل حاضر و آماده نباید بود. خود ASP.NET MVC هم تحت مجوز MSPL به صورت سورس باز در دسترس است.


و یک نکته تکمیلی
مایکروسافت مدتی است شروع کرده به پرورش و زمزمه ایده «یک ASP.NET واحد». به عبارتی قصد دارند در یکی دو نگارش بعد، این دو (وب فرم و MVC) را یکی کنند. هم اکنون اگر مطالب وبلاگ‌ها را مطالعه کنید زیرساخت آن به نام ASP.NET Web API آماده شده است و در مرحله بتا است. نکته جالب اینجا است که این Web API امکان تعریف یکپارچه و مستقیم کنترلر‌های MVC را در وب فرم‌ها میسر می‌کند. ولی باز هم نام آن Controller است یعنی جزئی از ASP.NET MVC و کسی می‌تواند از آن استفاده کند که با MVC‌ مشکلی نداشته باشد. بنابراین یادگیری MVC هیچ ضرری نخواهد داشت و جای دوری نخواهد رفت!



مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت دوم

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

مدل گزارش دهی

    /// <summary>    
    /// Repersents a Report template for every cms section
    /// </summary>
    public class Report
    {
        #region Ctor
        /// <summary>
        /// Create one instance for <see cref="Report"/>
        /// </summary>
        public Report()
        {
            ReportedOn = DateTime.Now;
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier for Report
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets reason of report
        /// </summary>
        public virtual string Reason { get; set; }
        /// <summary>
        /// gets or sets section that is reported
        /// </summary>
        public virtual ReportSection Section { get; set; }
        /// <summary>
        /// gets or sets sectionid that is reported
        /// </summary>
        public virtual long SectionId { get; set; }
        /// <summary>
        /// gets or sets type of report
        /// </summary>
        public virtual ReportType Type{ get; set; }
        /// <summary>
        /// gets or sets report's datetime
        /// </summary>
        public virtual DateTime ReportedOn { get; set; }
        /// <summary>
        /// indicate this report is read by admin
        /// </summary>
        public virtual bool IsRead { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets id of user that is reporter
        /// </summary>
        public virtual long ReporterId { get; set; }
        /// <summary>
        /// gets or sets id of user that is reporter
        /// </summary>
        public virtual User Reporter { get; set; }
        #endregion
    }

/// <summary>
    /// Represents Report Section
    /// </summary>
   public  enum  ReportSection
    {
        News,
        Poll,
        Announcement,
        ForumTopic,
        BlogComment,
        BlogPost,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        User,
      ...
    }

/// <summary>
    /// Represents Type of Report
    /// </summary>
    public enum  ReportType
    {
        Spam,
        Abuse,
        Advertising,
       ...
    }

قصد داریم در این سیستم به کاربران خاصی دسترسی گزارش دادن در بخش‌های مختلف را بدهیم. این دسترسی‌ها در بخش تنظیمات سیستم قابل تغییر خواهند بود (برای مثال براساس امتیاز ، براساس تعداد پست و ... ) . این امکان می‌تواند برای مدیریت سیستم مفید باشد.
برای سیستم گزارش دهی به مانند سیستم امتیاز دهی عمل خواهیم کرد. در کلاس Report، خصوصیت ReportSection  از نوع داده‌ی شمارشی می‌باشد که در بالا تعریف آن نیز آماده است و مشخص کننده‌ی بخش‌هایی می‌باشد که لازم است امکان گزارش دهی داشته باشند. خصوصیت Type هم که از نوع شمارشی ReportType می‌باشد، مشخص کننده‌ی نوع گزارشی است که داده شده است. 
علاوه بر نوع گزارش، می‌توان دلیل گزارش را هم ذخیره کرد که برای این منظور خصوصیت Reason در نظر گرفته شده‌است. خصوصیت IsRead هم برای مدیریت این گزارشات در پنل مدیریت در نظر گرفته شده است. اگر در مقاله‌ی قبل دقت کرده باشید، متوجه وجود خصوصیتی به نام ReportsCount در کلاس BaseContent و  BaseComment خواهید شد که برای نشان دادن تعداد گزارش‌هایی است که برای آن مطلب یا نظر داده شده است، استفاده می‌شود.

کلاس پایه فایل‌های ضمیمه

 /// <summary>
    /// Represents a base class for every attachment
    /// </summary>
    public abstract class BaseAttachment
    {
        #region Ctor

        public BaseAttachment()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            AttachedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets identifier for attachment
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// sets or gets name for attachment
        /// </summary>
        public virtual string FileName { get; set; }
        /// <summary>
        /// sets or gets type of attachment
        /// </summary>
        public virtual string ContentType { get; set; }
        /// <summary>
        /// sets or gets size of attachment
        /// </summary>
        public virtual long Size { get; set; }
        /// <summary>
        /// sets or gets Extention of attachment
        /// </summary>
        public virtual string Extension { get; set; }
        /// <summary>
        /// sets or gets bytes of data
        /// </summary>
        //public byte[] Data { get; set; }
        /// <summary>
        /// sets or gets Creation Date
        /// </summary>
        public virtual DateTime AttachedOn { get; set; }
        /// <summary>
        /// gets or sets counts of download this file
        /// </summary>
        public virtual long DownloadsCount { get; set; }
        /// <summary>
        /// gets or sets datetime that is modified
        /// </summary>
        public virtual DateTime ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets section that this file attached there
        /// </summary>
        public virtual AttachmentSection Section { get; set; }
        /// <summary>
        /// gets or sets information of user agent 
        /// </summary>
        public virtual string Agent { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets identifier of attachment's owner
        /// </summary>
        public virtual long OwnerId { get; set; }
        /// <summary>
        /// sets or gets identifier of attachment's owner
        /// </summary>
        public virtual User Owner { get; set; }
        #endregion
    }



    public enum  AttachmentSection
    {
        News,
        Announcement,
        ForumTopic,
        Conversation,
        BlogComment,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        BlogPost,
        Group,
        ...
    }

کلاس بالا اکثر خصوصیات لازم برای مدل Attachment ما را در خود دارد. قصد داریم از ارث بری TPH برای مدیریت فایل‌های ضمیمه استفاده کنیم. در سیستم بسته‌ی ما، تنها کاربران احراز هویت شده می‌توانند فایل ضمیمه کنند و برای همین منظور OwnerId را که همان ارسال کننده‌ی فایل می‌باشد، به صورت Nullable در نظر نگرفته‌ایم.
یک سری از مشخصات که نیاز به توضیح اضافی ندارند، ولی خصوصیت AttachmentSection که از نوع شمارشی AttachmentSection است، برای دسترسی راحت کاربر به فایل‌های ارسالی خود در پنل کاربری در نظر گرفته شده است. برای بخش‌های (وبلاگ - اخبار - نظرسنجی‌ها - آگهی‌ها - انجمن)  که نیاز به Privacy خاصی نیست و احراز هویت کفایت می‌کند، مدل زیر را در نظر گرفته ایم:

مدل فایل‌های ضمیمه عمومی

 /// <summary>
    /// Repersent the attachment for file
    /// </summary>
    public class Attachment : BaseAttachment
    {
       
    }
  مدل بالا صرفا برای بخش‌های مذکور کفایت خواهد کرد. در ادامه مقالات، برای بخش‌هایی مانند پیغام خصوصی، گروه‌هایی که کاربران ایجاد می‌کنند، برای انتشار تجربیات خود و هر بخشی که اضافه شود و نیاز به Privacy داشته باشد، نیاز خواهند بود تا مدل Attachment آنها با خود بخش هم در ارتباط باشد و تمام خصوصیت آنها که اکثرا کلید خارجی خواهند بود به صورت Nullable تعریف شوند.
مدل اخبار
 /// <summary>
    /// Represents one news item 
    /// </summary>
    public class NewsItem : BaseContent
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="NewsItem"/>
        /// </summary>
        public NewsItem()
        {
            Rating = new Rating();
            PublishedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// indicating that this news show on sidebar
        /// </summary>
        public virtual bool ShowOnSideBar { get; set; }
        /// <summary>
        /// indicate this NewsItem is approved by admin if NewsItem.Moderate==true
        /// </summary>
        public virtual bool IsApproved { get; set; }

        #endregion

        #region NavigationProperties

        /// <summary>
        /// gets or sets  newsitem's Reviews
        /// </summary>
        public ICollection<NewsComment> Comments { get; set; }

        #endregion
    }

                  کلاس بالا نشان دهنده‌ی اشتراک‌های ما خواهند بود. این مدل ما هم از کلاس پایه‌ی BaseContent بحث شده در مقاله‌ی قبل، ارث بری کرده و علاوه بر آن دو خصوصیت دیگر تحت عنوان IsApproved برای اعمال مدیریتی در نظر گرفته شده است (اگر در بخش تنظیمات سیستم اخبار، مدیریت تصمیم گرفته باشد تا اخبار جدید به اشتراک گذاشته شده با تأیید مدیریتی منتشر شوند) و خصوصیت ShowOnSideBar هم به عنوان یک تنظیم مدیریتی برای خبر خاصی در نظر گرفته شده که لازم است به صورت sticky در سایدبار نمایش داده شود.
برای اخبار نیز امکان ارسال نظر خواهیم داشت که برای این منظور لیستی از مدل زیر (NewsComment) در مدل بالا تعریف شده است .

مدل نظرات اخبار 

 public class NewsComment : BaseComment
    {
        #region Ctor
        public NewsComment()
        {
            Rating = new Rating();
            CreatedOn = DateTime.Now;

        }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// gets or sets body of blog NewsItem's comment
        /// </summary>
        public virtual long? ReplyId { get; set; }
        /// <summary>
        /// gets or sets body of blog NewsItem's comment
        /// </summary>
        public virtual NewsComment Reply { get; set; }
        /// <summary>
        /// gets or sets body of blog NewsItem's comment
        /// </summary>
        public virtual ICollection<NewsComment> Children { get; set; }
        /// <summary>
        /// gets or sets NewsItem that this comment sent to it
        /// </summary>
        public virtual NewsItem NewsItem { get; set; }
        /// <summary>
        /// gets or sets NewsItem'Id that this comment sent to it
        /// </summary>
        public virtual long NewsItemId { get; set; }
        #endregion
    }

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

مدل‌های پیغام خصوصی
/// <summary>
    /// Indicate one conversation
    /// </summary>
    public class Conversation
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="Conversation"/>
        /// </summary>
        public Conversation()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            SentOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier of record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// represents this conversaion is seen
        /// </summary>
        public virtual bool IsRead { get; set; }
        /// <summary>
        /// gets or sets subject of this conversation
        /// </summary>
        public virtual string Subject { get; set; }
        /// <summary>
        /// gets or sets Date that this record added
        /// </summary>
        public virtual DateTime SentOn { get; set; }
        /// <summary>
        /// indicate this record deleted by sender
        /// </summary>
        public virtual bool DeletedBySender { get; set; }
        /// <summary>
        /// indicate this record deleted by receiver
        /// </summary>
        public virtual bool DeletedByReceiver { get; set; }
        /// <summary>
        /// gets or sets Messagescount that Unread  by sender of this conversation
        /// </summary>
        public virtual int UnReadSenderMessagesCount { get; set; }
        /// <summary>
        /// gets or sets Messagescount that Unread  by receiver of this conversation
        /// </summary>
        public virtual int UnReadReceiverMessagesCount { get; set; }
        /// <summary>
        /// gets or sets Messagescount of this conversation for increase performance
        /// </summary>
        public virtual int MessagesCount { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets if of  user that start this conversation
        /// </summary>
        public virtual long SenderId { get; set; }
        /// <summary>
        /// gets or sets user that start this conversation
        /// </summary>
        public virtual User Sender { get; set; }
        /// <summary>
        /// gets or sets id of  user that is recipient
        /// </summary>
        public virtual long ReceiverId { get; set; }
        /// <summary>
        /// gets or sets   user that is recipient
        /// </summary>
        public virtual User Receiver { get; set; }
        /// <summary>
        /// get or set Messages of this conversation
        /// </summary>
        public virtual ICollection<ConversationReply> Messages { get; set; }
        /// <summary>
        /// get or set Attachments that attached in this conversation
        /// </summary>
        public virtual ICollection<ConversationAttachment> Attachments { get; set; }
        #endregion

مدل بالا نشان دهنده‌ی گفتگوی بین دو کاربر می‌باشد. هر گفتگو امکان دارد با موضوع خاصی ایجاد شود و مسلما یک کاربر به‌عنوان دریافت کننده و کاربر دیگری بعنوان ارسال کننده خواهد بود. برای این منظور خصوصیات Receiver و Sender که از نوع User هستند را در این کلاس در نظر گرفته‌ایم.
خصوصیات DeletedBySender و DeletedByReceiver هم برای این در نظر گفته شده‌اند که اگر یک طرف این گفتگو خواهان حذف آن باشد، برای آن کاربر حذف نرم انجام دهیم و فعلا برای کاربر مقابل قابل دسترسی باشد.
UnReadSenderMessagesCount و UnReadReceiverMessagesCount هم برای بالا بردن کارآیی سیستم در نظر گفته شده‌اند و در واقع تعداد پیغام‌های خوانده نشده در یک گفتگو به صورت متمایز برای هر دو طرف، ذخیره می‌شود. هر گفتگو شامل یکسری پیغام رد و بدل شده خواهد بود که بدین منظور لیستی از ConversationReply‌ها را در مدل بالا تعریف کرده‌ایم.
در هر گفتگو یکسری فایل هم ممکن است ضمیمه شود ، برای این منظور هم یک لیستی از کلاس ConversationAttachment در مدل گفتگو تعریف شده است که در ادامه پیاده سازی کلاس ConversationAttachment را هم خواهیم دید.   
مدل  ConversationReply به شکل زیر می‌باشد:

  /// <summary>
    /// Represents One Reply to Conversation
    /// </summary>
    public class ConversationReply
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="ConversationReply"/>
        /// </summary>
        public ConversationReply()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            SentOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier of record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// represents this conversaionReply is seen
        /// </summary>
        public virtual bool IsRead { get; set; }
        /// <summary>
        /// gets or sets body of this conversationReply
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets Date that this record added
        /// </summary>
        public virtual DateTime SentOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets  Parent's Id Of this ConversationReply
        /// </summary>
        public virtual Guid? ParentId { get; set; }
        /// <summary>
        /// gets or sets Parent Of this ConversationReply
        /// </summary>
        public virtual ConversationReply Parent { get; set; }
        /// <summary>
        /// get or set Children Of this ConversationReply
        /// </summary>
        public virtual ICollection<ConversationReply> Children { get; set; }
        /// <summary>
        /// gets or sets if of  user that start this conversationReply
        /// </summary>
        public virtual long SenderId { get; set; }
        /// <summary>
        /// gets or sets user that start this conversationReply
        /// </summary>
        public virtual User Sender { get; set; }
        /// <summary>
        /// gets or sets Conversation that this message sent in it 
        /// </summary>
        public virtual Conversation Conversation{ get; set; }
        /// <summary>
        /// gets or sets Id of Conversation that this message sent in it 
        /// </summary>
        public virtual Guid ConversationId { get; set; }
        #endregion
    }

مدل بالا نشان دهنده‌ی پیغام‌های داده شده در یک گفتگو با موضوعی خاص می‌باشد. ساختار درختی آن هم برای ایجاد امکان جواب دهی برای پیغام‌ها در نظر گرفته شده است (الزامی نیست). هر پیغام در یک گفتگو ارسال شده و یک ارسال کننده نیز دارد که برای این منظور به ترتیب دو خصوصیت Conversation از نوع کلاس Conversation و Sender از نوع User در نظر گرفته‌ایم.  
با توجه به وجود Privacy در گفتگو نیاز است تا مدل فایل ضمیمه بخش گفتگو‌ها به شکل زیر باشد:

/// <summary>
    /// Represents the attachment That attached in Conversation
    /// </summary>
    public class ConversationAttachment : BaseAttachment
    {
        #region NavigationProperties

        public virtual Conversation Conversation { get; set; }
        public virtual Guid? ConversationId { get; set; }
        #endregion
    }

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

نتیجه این قسمت

بازخوردهای دوره
استفاده از StructureMap به عنوان یک IoC Container
جهت رعایت بهتر نظم در سایت:
- هر دوره در سایت، یک قسمت مخصوص پرسش و پاسخ‌های شخصی مرتبط با آن دوره دارد.
- دوره جاری یک قسمت مجزای MVC دارد.
- نحوه ارسال یک گزارش خطای خوب را هم یکبار مطالعه کنید. ارسال stack trace و اصل خطای حاصل خیلی مهم است و بدون آن پاسخ دادن از راه دور، بسیار مشکل.
اشتراک‌ها
افزونه محاسبه میزان ساعت کار بر روی پروژه در VSCode
  • محاسبه ساعت کارکرد به تفکیک ساعت و روز هفته
  • محاسبه زمان کار بر روی پروژه در محل کار و خارج از محل کار
  • محاسبه تعداد خط‌های اضافه شده و حدف شده
  • محاسبه سرعت کد نویسی و ...
  • گزارش به دو صورت فایل و پنل کاربری



افزونه محاسبه میزان ساعت کار بر روی پروژه در VSCode
اشتراک‌ها
پلاگین jQuery برای استفاده در گزارش ساز های تحت وب (jui_filter_rules)

پلاگین jQuery جهت فیلتر کردن اطلاعات و استفاده در گزارش ساز‌های تحت وب ( پیش نمایش )

(jui_filter_rules is a jQuery Data filtering plugin (query builder

مدل دیگری از آن افزونه‌ی jQuery QueryBuilder می‌باشد


پلاگین jQuery برای استفاده در گزارش ساز های تحت وب (jui_filter_rules)
اشتراک‌ها
تبعات ممنوعیت واردات در حوزه رایانه/ افزایش 5 برابری قیمت در بازار
به گزارش خبرنگار مهر، اواخر هفته گذشته و با تصمیم دولت، ثبت سفارش واردات 75 قلم کالا که با عنوان "لوکس" و "غیرضرور" اعلام شده است به کشور متوقف شد که در این فهرست کالاهای دیجیتالی و رایانه ای نیز به چشم می‌خورد ...
تبعات ممنوعیت واردات در حوزه رایانه/ افزایش 5 برابری قیمت در بازار
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 15 - بررسی تغییرات Caching
این مورد را باید به صورت یک خطا، به تیم استیمول ارجاع بدید تا درستش کنند. تنظیم Size کش، هم توسط EF-Core و هم توسط ASP.NET Core در قسمت‌های خاصی انجام می‌شوند و .... مابقی باید خودشان را با این سیستم جدید وفق دهند. البته این تغییرات آنچنان جدید هم نیستند؛ بنابراین بهتر است قبل از گزارش خطا به آن‌ها، از آخرین نگارش استیمول استفاده کنید.
نظرات مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
با سلام؛ گزارش خطاهای سطح مدل (مثلا فرمت اشتباه کد ملی یا شماره تماس ) که قبلا به صورت 
this.ModelState.AddModelError("","کد ملی را درست وارد نمایید")
return View(model)
اتفاق می‌افتاد، به صورت غیر اسکریپتی و به شکل معمول امکان پذیر نمی‌باشد،