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

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

ابتدا نیاز است در سایت bintray ثبت نام کنید و با حساب جدید وارد شوید و گزینه‌ی maven را انتخاب کنید.

سپس روی گزینه‌ی Add New Package کلیک کنید تا یک پکیج جدید را ایجاد کنیم.

در صفحه‌ای که باز می‌شود، اطلاعات مربوط به این پکیج را وارد کنید که عموما شامل نام پکیج، مجوز آن، کلمات کلیدی، لینک گزارش باگ و .. می‌شود. در انتخاب نام پکیج، قانون اجباری یا خاصی وجود ندارد؛ ولی توصیه می‌شود که از حروف کوچک و - استفاده گردد. بعد از پرکردن فیلدهای الزامی، وارد صفحه‌ی جزئیات پکیج می‌شوید که در آن فیلدهای اضافه‌تری نیز وجود دارند که میتوانید در صورت تمایل آن‌ها را پر کنید. همچنین در بالای صفحه لینک به صفحه‌ی اختصاصی این پکیج نیز وجود دارد که در زیر عبارت Edit Package قرار گرفته است.

وارد صفحه‌ی اختصاصی پکیج شوید، می‌بینید که قسمت‌های مختلفی در اختیار شما قرار گرفته است و این مخزن الان آماده و پذیرای کتابخانه‌ی شماست. تا به اینجا، مرحله‌ی کار با jcenter تمام شد. حال به سایت SonaType می‌رویم و در آن هم ثبت نام می‌کنیم.


پی نوشت : اگر قصد آپلود کتابخانه‌ی خود را در این سایت ندارید، می‌توانید این سوال و مرحله‌ی امضای خودکار را از مراحل کاری خود حذف کنید.

سوال: چگونه این فایل را در SonaType آپلود کنیم؟ 
گام اول: ابتدا باید در سایت ثبت نام کنید. پس به این صفحه رفته و ثبت نام کنید. سپس در یک مرحله‌ی غیرمنطقی باید یک issue توسط سیستم JIRA ایجاد کنید. برای همین گزینه‌ی Creare را در بالای صفحه بزنید. اطلاعات زیر را به ترتیب پر کنید:
Project: Community Support - Open Source Project Repository Hosting

Issue Type: New Project

Summary: مثلا نام پروژه خودتان را بنویسید

یک نام پکیج که سعی کنید کتابخانه‌های هم خانواده این اشتراک را داشته باشند که در یک گروه قرار بگیرند
Group Id: AndroidBreadCrumb.Plus

آدرس جایی که پروژه قرار دارد
Project URL: https://github.com/yeganehaym/AndroidBreadCrumb

//آدرس سیستم کنترل نسخه
SCM url: https://github.com/yeganehaym/AndroidBreadCrumb
بقیه‌ی موارد الزامی نیست. کادر را تایید کنید. در این مرحله باید مدتی منتظر بمانید تا این مخزن را برای شما تایید کنند که طبق مستندات حدود یک هفته‌ای طول می‌کشد. بعد از اینکار باید نام کاربری اکانت خود را در پروفایل سایت Bintray  در برگه‌ی Accounts  قسمت sonatype oss account وارد کنید و پروفایل را آپدیت کنید.

فعال سازی امضای خودکار در Bintray

همانطور که در ابتدای مقاله گفتیم، می‌خواهیم کتابخانه‌ی خود را از طریق jcenter به maven ارسال کنیم. برای همین نیاز داریم که ابتدا کتابخانه‌ی خود را امضا کنیم. برای اینکار باید از طریق GPG یک کلید بسازیم. ساخت کلید به این شیوه، قبلا در مقاله‌ی «ساخت کلیدهای امنیتی با GnuPG» توضیح داده شد و از تکرار آن خودداری می‌کنیم. تنها به ذکر این نکته بسنده می‌کنیم که شما باید یک کلید ساخته و آن را به سرور کلیدها ارسال کنید و سپس کلید متنی عمومی و خصوصی آن را در پروفایل bintray برگه‌ی GPG Signing درج کنید.


به عنوان آخرین کار باید امضای خودکار را فعال کنید. بنابراین نیاز است که به صفحه‌ی اصلی رفته و بر روی maven کلیک کنید و گزینه‌ی Edit را انتخاب کنید. در صفحه‌ی باز شده، تیک GPG sign uploaded files automatically را بزنید.

این تنظیم از این پس بر روی تمامی کتابخانه‌ها اعمال می‌شود.


سوال : چگونه پروژه‌ی اندرویدی خودم را کامپایل کنم؟


فایل  build.gradle پروژه را باز کنید و پلاگین bintray را به آن معرفی کنید:
 dependencies {
        classpath 'com.android.tools.build:gradle:1.2.2'
        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2'
        classpath 'com.github.dcendents:android-maven-plugin:1.2'
    }
بسیار مهم است که نسخه‌ی gradle build tools از نسخه‌ی 1.1.2 به بعد باشد. چون که در نسخه‌های پایین‌تر، یک باگ بسیار حیاتی یافت شده، که از این نسخه به بعد رفع گردیده است. حال فایل local.properties را باز کنید و اطلاعات زیر را وارد کنید:
bintray.user=YOUR_BINTRAY_USERNAME
bintray.apikey=YOUR_BINTRAY_API_KEY
bintray.gpg.password=YOUR_GPG_PASSWORD
نام کاربری که برای اکانت bintray انتخاب کردید را بنویسید. برای ApiKey در قسمت Edit Profile برگه‌ی Api Key در خط دوم می‌توانید آن را ببینید و کلمه‌ی عبور GPG هم همان عبارتی است که در انتهای ساخت کلید دوبار از شما پرسیده شده است.  (در صورتی که اقدامات لازم برای mavenCentral را انجام داده اید خط سوم را  وارد کنید)

در مرحله‌ی بعدی خطوط زیر را بعد از 'Apply Plugin 'com.android.library اضافه کنید و اطلاعاتی که در bintray وارد کرده‌اید را در اینجا وارد کنید:
apply plugin: 'com.android.library'

ext {
    bintrayRepo = 'maven'
    bintrayName = 'AndroidBreadCrumb'

    publishedGroupId = 'com.plus'
    libraryName = 'AndroidBreadCrumb'
    artifact = 'AndroidBreadCrumb'

    libraryDescription = 'create breadcrumb on android to show a path to user and let user to jump on them'

    siteUrl = 'https://github.com/yeganehaym/AndroidBreadCrumb'
    gitUrl = 'https://github.com/yeganehaym/AndroidBreadCrumb'

    libraryVersion = '1.0'

    developerId = 'yeganehaym'
    developerName = 'ali yeganeh.m'
    developerEmail = 'yeganehaym@gmail.com'

    licenseName = 'The Apache Software License, Version 2.0'
    licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
    allLicenses = ["Apache-2.0"]
}
بعد از آن هم در انتهای فایل گریدل ماژول، دو خط زیر را که شامل اسکریپت‌هایی برای راحتی کار است، معرفی کنید:
apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
با اطلاعاتی که بالا تعریف کردیم، کاربر باید شناسه‌ای به شکل زیر در گریدل به کار ببرد تا به کتابخانه‌ی ما دسترسی داشته باشد:
compile 'com.plus:AndroidBreadCrumb:1.0'

آپلود فایل‌ها به مخزن
برای آپلود فایل‌های ماژول به مخزن، ابتدا ترمینال اندروید استودیو را باز کنید و گام‌های زیر را به ترتیب انجام بدهید:
گام اول: با ارسال دستور زیر از صحت کدها و منابع مطمئن می‌شویم:
gradlew install
در صورتیکه خطایی نباشد، پیام موفقیت آمیزی را به شما نمایش می‌دهد:
BUILD SUCCESSFUL
حالا باید فایل‌ها را به سمت سرور ارسال کنیم:
gradlew bintrayUpload
در صورتی که عملیات موفقیت آمیز باشد پیام زیر نمایش می‌یابد:
SUCCESSFUL

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



و قسمت فایل‌ها هم دیگر خالی نیست:




با اینکه کتابخانه‌ی ما روی maven قرار گرفت، ولی هنوز نمی‌توان آن را توسط jcenter استفاده کرد و باید bintray maven را با jcenter هماهنگ نماییم. در حال حاضر استفاده از این کتابخانه بدون سینک به شکل زیر است:

گریدل پروژه
     maven{
            url 'https://dl.bintray.com/yeganehaym/maven'
        }

گریدل ماژول

dependencies {
    compile 'com.plus:AndroidbreadCrumb:1.0'
}
آدرس اولی را می‌توانید با نگاه در صفحه‌ی اختصاصی پکیج ببینید که لینک آن در جلوی نام پکیج وجود دارد و قابلیت کپی کردن لینک هم وجود دارد. اگر روی آن کلیک کنید، می‌توانید مسیر را در صفحه‌ی باز شده ببینید. البته راه مستقیم‌تر اینکه به جای yeganehaym در لینک بالا، نام کاربری خودتان را جایگزین کنید.

برای افزودن کتابخانه‌ی خود به سیستم jcenter با کلیک بر روی گزینه‌ی Add to jcenter می‌توانید به تیم jcenter درخواست دهید که آن را تایید کنند که بعد از درخواست حدود سه ساعت طول می‌کشد تا پاسخ شما را بدهند.




به این ترتیب دیگر نیازی به تعریف یک url به maven نخواهد بود.

برای دیدن این کتابخانه در صفحه jcenter به ترتیب شناسه‌های Group_ID.Artifact.version را دنبال کنید، یعنی برای ما می‌شود:

com/plus/androidbreadcrumb/1.0
نکته اول : این نکته را به یاد داشته باشید که عمل سینک تنها یکبار اتفاق می‌افتد و در زمان‌های بعد، هر تغییری مثل حذف، به روز آوری و ... مستقیما در jcenter بعد از چند دقیقه اعمال می‌شود.

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


ارسال کتابخانه به mavenCentra
 در این مرحله قصد داریم که این کتابخانه را بر روی mavenCentral هم داشته باشیم. اگر قصدش را ندارید از اینجا به بعد را نیازی نیست انجام بدهید و برای اینکار لازم است همه‌ی مراحل بالا انجام گرفته باشد.
قبل از اینکه این عمل ارسال انجام گیرد، باید دو عمل زیر از قبل صورت گرفته باشند:
  1. پکیج شما در jcenter تایید شده باشد.
  2. با مخزن شما در sonatype موافقت شده باشد.

در صورتیکه دو مرحله‌ی بالا صورت گرفته باشند، در صفحه‌ی پکیج اختصاصی، بر روی گزینه‌ی mavenCentral کلیک کنید:

پس از آن باید نام کاربری و کلمه‌ی عبورتان را در SonaType، وارد کنید و گزینه‌ی sync را بفشارید:

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

مطالب
نحوه ایجاد یک اسلایدشو به صورت داینامیک

در این آموزش قصد دارم نحوه ایجاد یک Slideshow به صورت داینامیک را با ASP.NET طراحی کنم(منظور از ایجاد Slideshow به صورت داینامیک این است که عکس‌ها را به صورت داینامیک از DB بخواند).

اولین گام این است که Plugin مورد نظر را دریافت کنید که من از پلاگین Orbit استفاده کرده ام

ابتدا یک DataBase با نام DynamicSlideShow ایجاد و یک جدول با ساختار زیر با نام Picturesدرون آن ایجاد می‌کنیم

گام بعدی ایجاد یک پروژه Asp.Net با زبان C# و اضافه کردن فایلهای پلاگین به پروژه و ایجاد یک Handler برای بازیابی داده‌ها از DB است. ساختار   Solutionما باید به صورت زیر باشد

برای اینکه بتوانیم با DB ارتباط برقرار کنیم از EF استفاده می‌کنیم به همین منظور ابتدا یک Model  به نام DynamicSlideShowModel ایجاد می‌کنیم

در گام بعد بر روی GenerateFromDatabase  انتخاب کرده و بر روی دکمه Next کلیک می‌کنیم و در مراحل بعد ابتدا DB مربوط به مثال (DynamicSlideShow) و جدول آن را انتخاب می‌کنیم . حال یک Model به درون پوشه App_Code  اضافه شده است  

در ادامه برای واکشی رکوردهای موجود در جدول  Pictures  کدهای زیر را درون Handler می‌نویسیم

  var ctx = new DynamicSlideShowEntities();
  var list = ctx.Pictures.ToList();
  string str = JsonConvert.SerializeObject(list);
  context.Response.Write(str);

  

در این کدها تنها نکته‌ای که احتیاج به توضیح دارد این است که ابتدا یک لیست از رکوردها را از جدول Pictures واکشی می‌کنیم و برای پاس دادن به کلاینت ما آن‌ها را به فرمت Json تبدیل کرده ایم که برای تبدبل از کنابخانه آماده Newtonsoft.Json.dll استفاده کرده ایم 

حال باید با استفاده از jQuery کدهای درون Handler را اجرا کنیم؛ برای همین منظور یک صفحه با نام default.aspx ایجاد کرده و کدهای زیر را درون آن می‌نویسیم

<head runat="server">
    <title>Dynamic SlideShow</title>
    <link href="CSS/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Script/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.PicturePath + '" alt="' + b.PictureText + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"

            });
            });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
</body>

در اینجا ابتدا با استفاده از متد ajax کتابخانه jQuery کدهای درون Handler را اجرا کرده و به ازای هر المان موجود در جدول یک تگ img به صفحه اضافه می‌کنیم.

مطالب
طرحبندی صفحات وب با بوت استرپ 4 - قسمت اول
یکی از مفیدترین قسمت‌های بوت استرپ 4، سیستم طرحبندی و گرید آن است که در ابتدا با یک Container آغاز می‌شود. این Container می‌تواند واکنشگرا و با عرض ثابت باشد و یا fluid انتخاب شود که کل عرض صفحه را به خود اختصاص می‌دهد. داخل هر Container می‌توان ستون‌ها و ردیف‌هایی را تعریف کرد. بوت استرپ 4، یک گرید طرحبندی 12 ستونه را ارائه می‌دهد که برای اندازه‌های زیر، تعدادی break-point را تعریف کرده‌است:
extra small, small, medium, large, extra large
در این نگارش، break-point از نوع extra small، نسبت به نگارش سوم بوت استرپ، جدید است.
به این ترتیب این گرید به شدت انعطاف پذیر شده و می‌توان برای اندازه‌های مختلف صفحه، طرحبندی‌های متفاوتی را ارائه داد.


دربرگیرنده‌ها و ردیف‌های گرید بوت استرپ

گرید طرحبندی بوت استرپ 4، 12 ستونه است و از فناوری خاصی به نام Flexbox استفاده می‌کند. برای کار با آن نیاز است با دو عنصر اصلی زیر آشنا بود:
- Containers: هدف آن‌ها تنظیم طرحبندی، مطابق با break-point (های) خاصی می‌باشد.
- ردیف‌ها و ستون‌ها: طرحبندی اصلی را تشکیل می‌دهند و ردیف‌ها، تعریف ستون‌ها را میسر می‌کنند.


بررسی Grid Containers

در بوت استرپ 4، دو نوع Container با کلاس‌های زیر وجود دارند:
- container: یک دربرگیرنده‌ی متداول است و طرحبندی را در میانه‌ی صفحه و بر اساس break-point (های) خاصی نمایش می‌دهد. این دربرگیرنده، در اطرف آن یک padding با اندازه‌ی 15px را نیز به همراه دارد و با break-pointهای زیر خودش را تطبیق می‌دهد:
extra small: کمتر از 576px
small: بیشتر از 576px
medium: بیشتر از 768px
large: بیشتر از 992px
extra-large: بیشتر از 1200px

- container-fluid: این دربرگیرنده، کل عرض نمایشی صفحه را پوشش می‌دهد.


یک مثال: تعریف container، row و columns

<head>
    <style>
        img {
          width: 100px;
          display: block;
        }
      </style>
</head>

<body>
    <header class="clearfix" style="height: 50vh; background: url(images/background.jpg) no-repeat center center; background-size: cover; margin-bottom: 20px;">
        <div class="container">
            <img src="images/wisdompetlogo.svg" alt="Wisdom Pet Logo">
        </div>
    </header>

    <div class="container">
        <section id="services">
            <div class="row">
                <article class="col">
                    <img class="mx-auto" src="images/icon-exoticpets.svg" alt="Icon">
                    <h3>Exotic Pets</h3>
                    <p>We offer specialized care for reptiles, rodents, birds,
                        and other exotic pets.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-grooming.svg" alt="Icon">
                    <h3>Grooming</h3>
                    <p>Our therapeutic grooming treatments help battle fleas,
                        allergic dermatitis, and other challenging skin
                        conditions.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-health.svg" alt="Icon">
                    <h3>General Health</h3>
                    <p>Wellness and senior exams, ultrasound, x-ray, and dental
                        cleanings are just a few of our general health
                        services.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-nutrition.svg" alt="Icon">
                    <h3>Nutrition</h3>
                    <p>Let our nutrition experts review your pet's diet and
                        prescribe a custom nutrition plan for optimum health
                        and disease prevention.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-pestcontrol.svg" alt="Icon">
                    <h3>Pest Control</h3>
                    <p>We offer the latest advances in safe and effective
                        prevention and treatment of fleas, ticks, worms, heart
                        worm, and other parasites.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-vaccinations.svg" alt="Icon">
                    <h3>Vaccinations</h3>
                    <p>Our veterinarians are experienced in modern vaccination
                        protocols that prevent many of the deadliest diseases
                        in pets.</p>
                </article>
            </div>
        </section>
    </div>
</body>
با این خروجی:


توضیحات:
- برای آزمایش این مثال، ابتدا کلاس container آن‌را حذف کنید:
<div class="container">
مشاهده خواهید کرد که کل صفحه بجای نمایش در وسط آن، از سمت چپ و بدون هیچ فاصله‌ای شروع می‌شود. این کلاس container است که محتوا را به میانه‌ی صفحه با فواصلی معین از دو طرف، منتقل می‌کند. به همین جهت عنصرheader  ابتدای آن‌را داخل این container قرار نداده‌ایم تا کل عرض صفحه را پر کند. مقدار height: 50vh، به معنای استفاده‌ی از 50 درصد view-port height است.
اما چون می‌خواهیم محل قرارگیری لوگوی داخل این هدر، با حاشیه‌ی سمت چپ container ذیل آن یکی باشد، این logo را داخل container قرار داده‌ایم.
- از این جهت که تصاویر استفاده شده از نوع svg هستند و بسیار بزرگ می‌باشند، با style تعریف شده‌ی در ابتدای head، اندازه‌ی آن‌ها را کاهش داده‌ایم و همچنین نوع نمایشی آن‌ها را نیز به block تنظیم کرده‌ایم. در این حالت برای اینکه تصاویر در میانه‌ی این block قرار گیرند، از کلاس mx-auto استفاده شده‌است.
- در مرحله‌ی بعد، کار تعریف سطرها و ستون‌ها انجام شده‌است:
    <div class="container">
        <section id="services">
            <div class="row">
                <article class="col">
هر row باید داخل container قرار گیرد. سپس داخل آن می‌توان 12 ستون را تعریف کرد که در اینجا از کلاس col برای این منظور استفاده شده‌است. col ساده‌ترین نوع ستون در بوت استرپ 4 است. کار آن این است که محتوا را تا جائیکه می‌تواند در فضای مهیا نمایش دهد.


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


انواع ستون‌های طرحبندی در بوت استرپ 4

همانطور که عنوان شد، گرید بوت استرپ 4، دارای 12 ستون است. البته در اینجا یک ستون می‌تواند عرض چندین ستون را نیز به خود اختصاص دهد.

در این تصویر، فرمول تعریف کلاس‌های ستون‌ها را در بوت استرپ 4 مشاهده می‌کنید. هر قسمتی از این فرمول که داخل پرانتز قرار گرفته‌است، یعنی ذکر آن اختیاری می‌باشد؛ به همین جهت در مثال قبلی، ذکر صرفا col نیز کار کرد. در این حالت اگر دو ستون را تعریف کنید، هر کدام 50 درصد عرض container را به خود اختصاص می‌دهند و اگر سه ستون تعریف شود، هر کدام 33 درصد عرض را اشغال می‌کنند.
در این فرمول BP به معنای break-point است و یکی از مقادیر ذکر شده‌ی مقابل آن‌را می‌تواند داشته باشد. برای مثال اگر col-sm را تعریف کردیم، یعنی این ستون تا زمانیکه اندازه‌ی صفحه تا 576px باشد، کل عرض صفحه را پر می‌کند.
COL در اینجا به معنای تعداد ستونی است که این محتوا قرار است به خود اختصاص دهد. برای مثال col-md-6 یعنی این ستون تا رسیدن به break-point از نوع md، کل عرض صفحه را به خود اختصاص می‌دهد؛ پس از آن (اندازه‌ی صفحه‌ی بیشتر از 768px) این ستون، 6 واحد از 12 واحد ممکن را به خود اختصاص خواهد داد.
ذکر BP نیز اختیاری است. یعنی اگر تنها col-6 را تعریف کردیم، این ستون در تمام break-pointها و در تمام اندازه‌های ممکن صفحه، همواره 6 واحد از 12 واحد موجود را به خود اختصاص می‌دهد.

مثال: بررسی فرمول تعریف ستون‌ها در بوت استرپ 4

<head>
    <style>
        img {
              width: 100%;
              height: 200px;
              max-height: 200px;
            }
    </style>
</head>

<body>
    <div class="container">
        <div id="services">
            <div class="row">
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Exotic Pets</h4>
                    <p>We offer <strong>specialized</strong> care for <em>reptiles,
                            rodents, birds,</em> and other exotic pets.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Grooming</h4>
                    <p>Our therapeutic <span class="font-weight-bold">grooming</span>
                        treatments help battle fleas, allergic dermatitis, and
                        other challenging skin conditions.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>General Health</h4>
                    <p>Wellness and senior exams, ultrasound, x-ray, and dental
                        cleanings are just a few of our general health
                        services.</p>
                </section>
                <section class="col">
                    <img class="img-fluid" src="images/image.png" alt="sample image">
                    <h4>Nutrition</h4>
                    <p>Let our nutrition experts review your pet's diet and
                        prescribe a custom nutrition plan for optimum health
                        and
                        disease prevention.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Pest Control</h4>
                    <p>We offer the latest advances in safe and effective
                        prevention and treatment of fleas, ticks, worms, heart
                        worm, and other parasites.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Vaccinations</h4>
                    <p>Our veterinarians are experienced in modern vaccination
                        protocols that prevent many of the deadliest diseases
                        in
                        pets.</p>
                </section>
            </div>
        </div>
    </div>
</body>
- ابتدا یک container تعریف شده‌است تا محتوا را به میانه‌ی صفحه منتقل کند و همچنین شیوه‌نامه‌های پیش‌فرض بوت استرپ را به آن اعمال نماید.
- سپس کل محتوا را داخل یک ردیف تعریف کرده‌ایم.
- در ادامه هر المان section تعریف شده را به صورت یک col در آورده‌ایم. به این ترتیب بوت استرپ سعی می‌کند تا کل عرض را با ستون‌های تعریف شده تا جائیکه ممکن است پر کند:


و اگر عرض صفحه را کوچک‌تر کنیم، باز هم تاجائیکه ممکن است تعدادی را در سطر اول و مابقی را در سطرهای بعدی جا خواهد داد:


در ادامه می‌خواهیم مشخص کنیم، ستون‌های خاصی، همیشه عرض مشخصی را به خود اختصاص دهند:
    <div class="container">
        <div id="services">
            <div class="row">
                <section class="col-6">
با این خروجی:


در این حالت، اولین ستون تعریف شده، تحت هر حالتی و با هر اندازه‌ی صفحه‌ای، همیشه نصف عرض (6 واحد از 12 واحد) را به خود اختصاص خواهد داد.

مرحله‌ی بعد این است که مشخص کنیم یک ستون در چه اندازه‌ای از صفحه شروع کند به اختصاص دادن عرضی خاص به خود. برای این منظور هر 6 عنصر section تعریف شده را به صورت زیر ویرایش می‌کنیم:
<section class="col-sm">
در این حالت اگر اندازه‌ی صفحه کمتر از این break-point باشد، هر ستون، کل عرض صفحه را به خود اختصاص می‌دهد:


و اگر صفحه را بزرگتر کنیم، مجددا همان حالت تعریف col خالی تکرار می‌شود و بوت استرپ سعی می‌کند در هر سطر، تا جائیکه می‌تواند، تعداد آیتم بیشتری را جا دهد. اگر بخواهیم این تعداد جا دادن‌ها را نیز کنترل کنیم، می‌توان به صورت زیر عمل کرد:
<section class="col-sm-6">
در اینجا col-sm-6 را به هر المان section انتساب داده‌ایم. به این ترتیب باز هم اگر عرض صفحه کمتر از sm باشد، هر آیتم، کل عرض صفحه را به خود اختصاص می‌دهد. اما اگر اندازه‌ی صفحه بیشتر از sm شود، هر آیتم همواره 6 واحد، یا نیمی از عرض را به خود اختصاص خواهد داد:



امکان تعریف بیش از یک break-point برای هر ستون در بوت استرپ 4


در این جدول انواع break-pointهای قابل تعریف توسط بوت استرپ 4 را ملاحظه می‌کنید. تا اینجا تاثیر اعمال تنها یکی از این‌ها را بر روی یک ستون بررسی کردیم؛ اما می‌توان چندین break-point را بر روی یک ستون نیز اعمال کرد. برای مثال اگر به تمام sectionهای مثال این قسمت ترکیب زیر را اضافه کنیم:
<section class="col-sm-6 col-md-4">
به این معنا خواهد بود که تا زمانیکه عرض صفحه کمتر از 576px است (sm)، هر آیتم کل عرض صفحه را پوشش می‌دهد. زمانیکه از این break-point رد شدیم، هر آیتم 6 واحد از 12 واحد را به خود اختصاص خواهد داد؛ تا زمانیکه عرض صفحه کمتر از 768px است (md). پس از اینکه این break-point را نیز رد کردیم و اندازه‌ی صفحه بزرگتر از آن شد، اینبار هر آیتم، 4 واحد از 12 واحد را به خود اختصاص می‌دهد:



امکان تغییر موقعیت شروع ستون‌ها در بوت استرپ 4


در تصویر فوق، فرمول تغییر موقعیت شروع ستون‌ها در بوت استرپ 4 را مشاهده می‌کنید. برای مثال اگر offset-sm-1 را به اولین section اضافه کنیم:
<section class="col-sm-6 col-md-4 offset-sm-1">
پس از رد شدن از break-point تعریف شده‌ی sm، اولین section، به اندازه‌ی یک واحد به سمت راست منتقل می‌شود و از لبه‌ی سمت چپ فاصله پیدا می‌کند:


برای نمونه این قابلیت، پیشتر یکی از روش‌های در مرکز صفحه قرار دادن ستون‌ها بود؛ اما چون بوت استرپ 4 از Flexbox استفاده می‌کند، روش‌های بهتری نیز برای آن وجود دارند که آن‌ها را در قسمت‌های بعد بررسی می‌کنیم.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_04.zip
مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت اول
قصد داریم طی یک سری مقالات به توسعه یک سیستم مدیریت محتوا بپردازیم. مسلما فاصله‌ی زمانی بین انتشار مقالات این سری، کمی زیاد خواهد بود. ولی سعی خواهیم کرد تا قدم به قدم و با تحلیل و توضیح کافی هر بخش به این هدف برسیم.
همکاران این قسمت:
پیشنیاز‌ها:
در زیر بخش هایی که برای این سیستم تا به امروز در نظر گرفتیم (مسلما با ارائه ایده‌ها و بازخورد‌های دوستان این امکانات دستخوش تغییر خواهند شد) ، به شرح زیر میباشد:
  • انجمن
  • ارتباط دوستی
  • سیستم ترفیع رتبه
  • Themeable
  • سیستم Following
  • صفحات داینامیک
  • سیستم پیام رسانی
  • امکان ساخت گروه‌های شخصی برای انتشار مطالب خود (توسط کاربران) با اعمال دسترسی مختلف
  • پیغام خصوصی
  • وبلاگ
  • نظرسنجی ها
  • مدیریت کاربران با دسترسی‌ها داینامیک
  • اخبار
  • آگهی ها
در این قسمت مدل‌های مربوط به بخش وبلاگ را بررسی میکنیم. ابتدا قصد داشتیم تا امکان نگارش یک پست توسط چندین کاربر را به طور همزمان، در سیستم قرار دهیم و ایده کار هم که توسط یکی دوستان (محمد شریفی) پیشنهاد شد به صورت زیر بود:
ابتدا کاربری به عنوان ایجاد کننده اصلی پست، بخش‌ها مختلفی را برای یک پست در نظر بگیرد و هر قسمت را به یک همکار نسبت دهد و با اتمام تمام بخش‌ها و اعلام آن توسط همکاران، کاربر اصلی ایجاد کننده پست بتواند بخش‌ها را باهم ادغام کند و برای آماده انتشار نهایی باشد.
ولی خب به نظر بنده الان سیستم‌های ارتباطی قدرتمندتری هم هست که همکاران در مورد نگارش یک پست به صورت مشارکتی بپردازند و صرفا در مرحله‌ی انتشار، این کاربران به عنوان کاربران همکار به پست منتشر شده نسبت داده شوند تا در زیر پست مشخصات کامل آنها قابل مشاهد باشد (راه حلی که پیاده سازی خواهیم کرد).
مدل‌های در نظر گرفته شده برای سیستم وبلاگ به صورت زیر می‌باشد:

مخزن برچسب ها
/// <summary>
    /// Represents the lable 
    /// </summary>
    public class Tag
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="Tag"/>
        /// </summary>
        public Tag()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets Tag's identifier
        /// </summary>
        public virtual Guid  Id { get; set; }
        /// <summary>
        /// sets or gets Tag's name
        /// </summary>
        public virtual string Name { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets Tag's posts
        /// </summary>
        public virtual ICollection<BlogPost> BlogPosts { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی مخزن برچسب‌های سیستم ما خواهد بود. جدول حاصل از این مدل با جداول سایر بخش‌ها که نیاز به داشتن برچسب خواهند داشت، ارتباط چند به چند خواهد داشت. برای این منظور همانطور که مشخص است در قسمت NavigationProperties یک لیست از BlogPost معرفی شده است و در ادامه خواهیم دید که لیستی از کلاس Tag هم در کلاس BlogPost معرفی خواهد شد.

مدل پیش نویس ها
  /// <summary>
    /// Represents the Post's Draft
    /// </summary>
    public  class BlogDraft
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="BlogDraft"/>
        /// </summary>
        public  BlogDraft()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of post's draft
        /// </summary>
        public virtual  Guid Id { get; set; }
        /// <summary>
        /// gets or sets body of post's draft
        /// </summary>
        public virtual  string Body { get; set; }
        /// <summary>
        /// gets or set title of post's draft
        /// </summary>
        public virtual  string Title { get; set; }
        /// <summary>
        /// gets or sets tags of post's draft that seperated using ','
        /// </summary>
        public virtual  string TagNames { get; set; }
        /// <summary>
        /// gets or sets value indicating whether this draft is ready to publish
        /// </summary>
        public virtual  bool IsReadyForPublish { get; set; }
        /// <summary>
        /// ges ro sets DateTime that this draft added
        /// </summary>
        public virtual  DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets date that this draft publish as ready
        /// </summary>
        public virtual DateTime? ReadyForPublishOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Id of user that he is owner of this draft
        /// </summary>
        public virtual  long OwnerId { get; set; }
        /// <summary>
        /// gets or sets user that he is owner of this draft
        /// </summary>
        public virtual  User Owner { get; set; }
        #endregion
    }
کلاس فوق برای ذخیره سازی پست‌های پیشنویس کاربران در نظر گرفته شده است. شاید بهتر بود این پیش نویس‌ها هم در همان مدل پستی که در ادامه مشاهده می‌کنید ادغام شوند. ولی این یک تصمیم شخصی برای این کار بوده و برای سیستی بزرگی که امکان دارد حذف و درج پیشنویس‌ها زیاد باشد و فرض بر اینکه long هم برای آی دی جواب گو نخواهد بود و نیاز است از Guid ای که به صورت متوالی افزایش میابد به منظور جلوگیری از Fragmentation، استفاده کرد. بقیه فیلد‌ها هم مشخص هستند و نیازی به توضیح اضافی نخواهد بود. مسلما هر کاربر می‌تواند چندین پیشنویس داشته باشد. لذا ارتباط یک به چند مابین کاربر و پیشنویس پست، خواهیم داشت.
مدل امتیاز دهی کاربران
/// <summary>
    /// Section of Rating
    /// </summary>
    public enum RatingSection
    {
        News,
        Announcement,
        ForumTopic,
        BlogComment,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        ...
    }


    /// <summary>
    /// Represents Rating Record regard by section type for Rating System
    /// </summary>
    public class UserRating
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="UserRating"/>
        /// </summary>
        public UserRating()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of Rating Record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets value of rate
        /// </summary>
        public virtual double RatingValue { get; set; }
        /// <summary>
        /// gets or sets Section's Id 
        /// </summary>
        public virtual long SectionId { get; set; }
        /// <summary>
        /// gets or sets Section 
        /// </summary>
        public virtual RatingSection Section { get; set; }
        #endregion

        #region Navigation Properties
        /// <summary>
        /// gets or sets user that rate one section
        /// </summary>
        public virtual User Rater { get; set; }
        /// <summary>
        /// gets or sets Rater Id that rate one section
        /// </summary>
        public virtual long RaterId { get; set; }

        #endregion
    }
نوع داده‌ی شمارشی RatingSection مشخص کننده‌ی بخش‌های مختلف سیستم می‌باشد که نیاز به امتیاز دهی دارند. چون سیستم ما یک سیستم بسته می‌باشد و برای امتیاز دهی نیاز به احراز هویت خواهد بود، لذا باید از امتیاز دادن بیش از یک بار برای هر بخش جلوگیری کنیم. برای این کار می‌توان بین تمام بخش‌های موجود ارتباط‌های چند به چند در نظر گرفت. برای مثال یک ارتباط چند به چند بین کاربر و نظرات که مشخص کننده‌ی این است که یک کاربر می‌تواند به چند نظر امتیاز دهد و هر نظر هم میتواند چندین امتیاز دهنده داشته باشد ولی تعداد جداول بالا خواهد رفت و کار آن هم زیاد خواهد شد. برای این منظور میتوان یک جدول به مانند UserRating در نظر گرفت که صرفا با جدول کاربر ما یک ارتباط یک به چند دارد و با استفاده از خصوصیت RatingSection موجود در این کلاس وخصوصیت SectionId و همچنین در نظر گرفتن یک کلید منحصر به فرد بر روی خصوصیت‌های RatingSection ، RaterId و SectionId، از امتیاز دادن چند باره‌ی کاربر به یک بخش جلوگیری کرد.
/// <summary>
    /// Represent the rating as ComplexType
    /// </summary>
    [ComplexType]
    public class Rating
    {
        /// <summary>
        /// sets or gets total of rating
        /// </summary>
        public virtual double? TotalRating { get; set; }
        /// <summary>
        /// sets or gets rater's count
        /// </summary>
        public virtual long? RatersCount { get; set; }
        /// <summary>
        /// sets or gets average of rating
        /// </summary>
        public virtual double? AverageRating { get; set; }
    }
کلاس بالا یک ComplexType می‌باشد که در نهایت به هیچ جدولی مپ نخواهد شد و صرفا به منظور کپسوله کردن یکسری فیلد مورد استفاده قرار گرفته است و از این کلاس می‌توان در هر بخش که لازم است امتیاز دهی داشته باشیم، به عنوان یک خصوصیت، استفاده کرد.

کلاس پایه‌ی محتوا
/// <summary>
    /// Represents a base class for every content in system
    /// </summary>
    public abstract class BaseContent
    {
        #region Properties

        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of publishing content
        /// </summary>
        public virtual DateTime PublishedOn { get; set; }
        /// <summary>
        /// gets or sets Last Update's Date
        /// </summary>
        public virtual DateTime ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets the blog pot body
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets the content title
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets value  indicating Custom Slug
        /// </summary>
        public virtual string SlugUrl { get; set; }
        /// <summary>
        /// gets or sets meta title for seo
        /// </summary>
        public virtual string MetaTitle { get; set; }
        /// <summary>
        /// gets or sets meta keywords for seo
        /// </summary>
        public virtual string MetaKeywords { get; set; }
        /// <summary>
        /// gets or sets meta description of the content
        /// </summary>
        public virtual string MetaDescription { get; set; }
        /// <summary>
        /// gets or sets 
        /// </summary>
        public virtual string FocusKeyword { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content use CanonicalUrl
        /// </summary>
        public virtual bool UseCanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets CanonicalUrl That the Post Point to it
        /// </summary>
        public virtual string CanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Follow for Seo
        /// </summary>
        public virtual bool UseNoFollow { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Index for Seo
        /// </summary>
        public virtual bool UseNoIndex { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content in sitemap
        /// </summary>
        public virtual bool IsInSitemap { get; set; }
        /// <summary>
        /// gets or sets a value indicating whether the content comments are allowed 
        /// </summary>
        public virtual bool AllowComments { get; set; }
        /// <summary>
        /// gets or sets a value indicating whether the content comments are allowed for anonymouses 
        /// </summary>
        public virtual bool AllowCommentForAnonymous { get; set; }
        /// <summary>
        /// gets or sets  viewed count by rss
        /// </summary>
        public virtual long ViewCountByRss { get; set; }
        /// <summary>
        /// gets or sets viewed count 
        /// </summary>
        public virtual long ViewCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.Approved).Count()
        /// We use this property for performance optimization (no SQL command executed)
        /// </remarks>
        /// </summary>
        public virtual int ApprovedCommentsCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.UnApproved).Count()
        /// We use this property for performance optimization (no SQL command executed)</remarks></summary>
        public virtual int UnApprovedCommentsCount { get; set; }
        /// <summary>
        /// gets or sets value  indicating whether the content is logical deleted or hidden
        /// </summary>
        public virtual bool IsDeleted { get; set; }
        /// <summary>
        /// gets or sets rating complex instance
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content show with rssFeed
        /// </summary>
        public virtual bool ShowWithRss { get; set; }
        /// <summary>
        /// gets or sets value indicating maximum days count that users can send comment
        /// </summary>
        public virtual int DaysCountForSupportComment { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets icon name with size 200*200 px for snippet 
        /// </summary>
        public virtual string SocialSnippetIconName { get; set; }
        /// <summary>
        /// gets or sets title for snippet
        /// </summary>
        public virtual string SocialSnippetTitle { get; set; }
        /// <summary>
        /// gets or sets description for snippet
        /// </summary>
        public virtual string SocialSnippetDescription { get; set; }
        /// <summary>
        /// gets or sets body of content's comment
        /// </summary>
        public virtual byte[] RowVersion { get; set; }
        /// <summary>
        /// gets or sets name of tags seperated by comma that assosiated with this content fo increase performance
        /// </summary>
        public virtual string TagNames { get; set; }
        /// <summary>
        /// gets or sets counter for Content's report
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Author { get; set; }

        /// <summary>
        /// gets or sets Id of user that create this record
        /// </summary>
        public virtual long AuthorId { get; set; }
        /// <summary>
        /// get or set  the tags integrated with content
        /// </summary>
        public virtual ICollection<Tag> Tags { get; set; }
        #endregion

    }

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

نکته‌ای که وجود دارد فیلد‌های ApprovedCommentsCount  UnApprovedCommentsCount و TagNames می‌باشند که هنگام درج نظر جدید باید تعداد نظرات ذخیره شده را  ویرایش کنیم و هنگام ویرایش خود پست یا خبر با ... و یا حتی ویرایش خود تگ یا حذف آن تگ باید TagNames که لیست برچسب‌های محتوا را به صورت جدا شده با (,) از هم دیگر می‌باشد، ویرایش کنیم (جای بحث دارد).

مشخص است که هر یک از مطالب منتشر شده در بخش‌های وبلاگ، اخبار، نظرسنجی و آگهی‌ها، یک کابر ایجاد کننده (Author نامیده‌ایم) خواهد داشت و هر کاربر هم می‌تواند چندین مطلب را ایجاد کند. لذا رابطه‌ی یک به چند بین تمام این بخش‌ها مذکور و کاربر ایجاد خواهد شد.

مدل  LinkBack

 /// <summary>
    /// Represents link for implemention linkback
    /// </summary>
    public class LinkBack
    {

        #region Ctor
        /// <summary>
        /// create one instance of <see cref="LinkBack"/>
        /// </summary>
        public LinkBack()
        {
            CreatedOn = DateTime.Now;
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets link's Id
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets text for show Link
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets link's address 
        /// </summary>
        public virtual string Url { get; set; }
        /// <summary>
        /// gets or set value indicating whether this link is internal o external
        /// </summary>
        public virtual LinkBackType Type { get; set; }
        /// <summary>
        /// gets or sets date that this record is added
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Post that associated
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets id of Post that associated
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }


 /// <summary>
    /// represents Type of ReferrerLinks
    /// </summary>
    public enum LinkBackType
    {
        /// <summary>
        /// Internal link
        /// </summary>
        Internal,
        /// <summary>
        /// External Link
        /// </summary>
        External
    }

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

مدل پست ها

  /// <summary> 
    /// Represents a blog post
    /// </summary>
    public  class BlogPost : BaseContent
    {
        #region Ctor
        /// <summary>
        /// Create one Instance of <see cref="BlogPost"/>
        /// </summary>
        public BlogPost()
        {
            Rating = new Rating();
            PublishedOn = DateTime.Now;
        }
        #endregion

       #region Properties
        /// <summary>
        /// gets or sets Status of LinkBack Notifications
        /// </summary>
        public virtual LinkBackStatus LinkBackStatus { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set  blog post's Reviews
        /// </summary>
        public virtual ICollection<BlogComment> Comments { get; set; }
        /// <summary>
        /// get or set collection of links that reference to this blog post
        /// </summary>
        public virtual ICollection<LinkBack> LinkBacks { get; set; }
        /// <summary>
        /// get or set Collection of Users that Contribute on this post
        /// </summary>
        public virtual ICollection<User> Contributors  { get; set; }
        
        #endregion
    }


  /// <summary>
    /// Represents Status for ReferrerLinks
    /// </summary>
    public enum  LinkBackStatus
    {
        [Display(Name ="غیرفعال")]
        Disable,
        [Display(Name = "فعال")]
        Enable,
        [Display(Name = "لینک‌ها داخلی")]
        JustInternal,
        [Display(Name = "لینک‌ها خارجی")]
        JustExternal
    }
 کلاس بالا نشان دهنده‌ی مدل پست‌های ما در سیستم می‌باشد که بیشتر خصوصیات خود را از کلاس پایه‌ی BaseContent به ارث برده و لازم به تکرار آنها نیست. به علاوه یکسری خصوصیات دیگر، از جلمه‌ی آنها یک لیست از کلاس LinkBack، لیستی از کاربران مشارکت کننده به منظور نمایش مشخصات آنها در زیر پست و لیستی از کلاس BlogComment که نشان دهنده‌ی نظرات پست می‌باشد، هم خواهد داشت. خصوصیتی از نوع داده‌ی LinkBackStatus هم صرفا به منظور تنظیمات مدیریتی در نظر گرفته شده است. 

کلاس پایه نظرات
  /// <summary>
    /// Represents a base class for every comment in system 
    /// </summary>

    public abstract class BaseComment
    {
        #region Properties
        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of creation 
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets displayName of this comment's Creator if  he/she is Anonymous
        /// </summary>
        public virtual string CreatorDisplayName { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets informations of agent
        /// </summary>
        public virtual string UserAgent { get; set; }
        /// <summary>
        /// gets or sets siteUrl of Creator if he/she is Anonymous
        /// </summary>
        public virtual string SiteUrl { get; set; }
        /// <summary>
        /// gets or sets Email of Creator if he/she is anonymous
        /// </summary>
        public virtual string Email { get; set; }
        /// <summary>
        /// gets or sets status of comment
        /// </summary>
        public virtual CommentStatus Status { get; set; }
        /// <summary>
        /// gets or sets Ip Address of Creator
        /// </summary>
        public virtual string CreatorIp { get; set; }
        /// <summary>
        /// gets or sets datetime that is modified
        /// </summary>
        public virtual DateTime? ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets counter for report this comment
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Creator { get; set; }

        /// <summary>
        /// get or set Id of user that create this record
        /// </summary>
        public virtual long? CreatorId { get; set; }
        #endregion
    }

public enum CommentStatus
    {
        /* 0 - approved, 1 - pending, 2 - spam, -1 - trash */
        [Display(Name = "تأیید شده")]
        Approved = 0,
        [Display(Name = "در انتظار بررسی")]
        Pending = 1,
        [Display(Name = "جفنگ")]
        Spam = 2,
        [Display(Name = "زباله دان")]
        Trash = -1
    }
به مانند BaseContent که برای کپسوله کردن خصوصیات تکراری و نه برای اعمال ارث بری TPH یا TPT، کلاس BaseComment را در نظر گرفته‌ایم. نکته‌ی مهم این است که هر نظری یک درج کننده دارد. ولی اگر فیلد AllowCommentForAnonymous مربوط به مطلب True باشد، این امکان وجود خواهد داشت تا کاربران احراز هویت نشده نیز نظر ارسال کنند. لذا CreatorId در کلاس BaseComment به صورت Nullable در نظر گرفته شده است و یک سری خصوصیت‌ها از قبیل SiteUrl ، CreatorDisplayName ، Email برای کاربران احراز هویت نشده در نظر گرفته شده است. نوع شمارشی CommentStatus نیز برای اعمال مدیریتی در نظر گرفته شده است.
مدل نظرات پست ها
/// <summary>
    /// Represents a blog post's comment
    /// </summary>
    public class BlogComment : BaseComment
    {
        #region Ctor
        /// <summary>
        /// Create One Instance for <see cref="BlogComment"/>
        /// </summary>
        public BlogComment()
        {
            Rating = new Rating();
            CreatedOn = DateTime.Now;
        }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets BlogComment's identifier for Replying and impelemention self referencing
        /// </summary>
        public virtual long? ReplyId { get; set; }
        /// <summary>
        /// gets or sets blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual BlogComment Reply { get; set; }
        /// <summary>
        /// get or set collection of blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual ICollection<BlogComment> Children { get; set; }
        /// <summary>
        /// gets or sets post that this comment sent to it
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets post'Id that this comment sent to it
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی نظرات پست‌های وبلاگ می‌باشند که بیشتر خصوصیت‌های خود را از کلاس پایه‌ی BaseComment به ارث برده است و همچنین ساختار سلسله مراتبی آن نیز  قابل مشاهده است. 
برای سیستم Report یا همان گزارش خطا هم سیستمی شبیه به امتیاز دهی ستاره‌ای در نظر گرفته ایم. برای اینکه مقاله خیلی طولانی شد، بهتر است در مقاله‌ای جدا کار را ادامه داد.
نتیجه این قسمت

مطالب
چگونه یک اسکریپت گریس مانکی بنویسیم؟

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

اسکریپت سلام دنیای گریس مانکی!
// ==UserScript==
// @name Hello World
// @namespace http://diveintogreasemonkey.org/download/
// @description example script to alert "Hello world!" on every page
// @include *
// @exclude http://diveintogreasemonkey.org/*
// @exclude http://www.diveintogreasemonkey.org/*
// ==/UserScript==
alert('Hello world!');

فرمت کلی و آناتومی یک اسکریپت گریس مانکی مطابق چند سطر فوق است. در ابتدا نامی را که برای اسکریپت مشخص کرده‌اید، ذکر خواهید نمود. سپس فضای نام آن مشخص می‌گردد. این فضای نام یک آدرس وب خواهد بود (مثلا سایت شخصی شما) . به این طریق گریس مانکی می‌تواند اسکریپت‌های هم نام را بر اساس این فضاهای نام مختلف مدیریت کند. سپس توضیحات مربوط به اسکریپت ارائه می‌شود. در قسمت include مشخص خواهید کرد که این اسکریپت بر روی چه سایت‌ها و آدرس‌هایی اجرا شود و در قسمت exclude سایت‌های صرفنظر شونده را تعیین خواهید نمود. در مثال فوق، اسکریپت بر روی تمامی سایت‌ها اجرا خواهد شد، منهای دو سایت و زیر سایت‌هایی که در قسمت exclude مشخص شده‌اند.
در پایان مثلا نام آن‌را hello.user.js خواهیم گذاشت (همانطور که ذکر شد قسمت user.js آن باید رعایت شود). برای نصب آن فقط کافی است این فایل را به درون پنجره فایر فاکس کشیده و رها کنیم (drag & drop).

استفاده از jQuery در اسکریپت‌های گریس مانکی

عمده کاربردهای اسکریپت‌های گریس مانکی در جهت اعمال تغییرات بر روی document object model صفحه (DOM) هستند. کتابخانه jQuery اساسا برای این منظور تهیه و بهینه سازی شده است.

مثال:
فرض کنید قصد داریم به نتایج خروجی جستجوی گوگل، fav icon سایت‌های یافت شده را اضافه کنیم (در کنار هر لینک، آیکون سایت مربوطه را نمایش دهیم). گوگل این‌کار را انجام نداده است. اما ما علاقمند هستیم که این قابلیت را اضافه کنیم!
// ==UserScript==
// @name Google FavIcon
// @namespace http://userscripts.org
// @description Shows favicon for Google searches results
// @include http://*.google.*/search?*

// ==/UserScript==

loadJquery();

$(document).ready(function(){
$("h3.r > a.l").each(function(){
var $a = $(this);
var href = $a.attr("href");
var domain = href.replace(/<\S[^><]*>/g, "").split('/')[2];
var image = '<img src="http://' + domain + '/favicon.ico" style="border:0;padding-right:4px;" />';
//GM_log(">image:> " + image);
$(this).prepend(image);
});

});

نحوه پیاده سازی تابع loadJquery را در سورس مربوطه می‌توانید مشاهده نمائید.
دریافت سورس این مثال

در اینجا ابتدا لینک‌های حاصل از جستجو پیدا می‌شوند. سپس نام دومین مربوطه استخراج می‌گردد (با استفاده از regular expressions) و در ادامه از این نام دومین یک آدرس استاندارد http://domain/favico.ico ساخته شده و از آن یک تگ img درست می‌شود. در آخر این تگ به قبل از لینک‌های گوگل اضافه می‌شود.

شاید سؤال بپرسید از کجا مشخص شد که باید به دنبال h3.r > a.l گشت؟ به تصویر زیر دقت نمائید (نمایی است از یکی از توانایی‌های افزونه fireBug فایرفاکس).



هنگامیکه صفحه بارگذاری شد، بر روی آیکون سوسک موجود در status bar فایرفاکس کلیک کنید تا FireBug ظاهر شود (البته من اینجا سوسک دیدم شاید موجود دیگری باشد :) )
سپس بر روی دکمه inspect در نوار ابزار آن کلیک کنید. در همین حال اشاره‌گر ماوس را به یکی از لینک‌های نتیجه جستجوی گوگل نزدیک کنید. بلافاصله تگ‌ها و کلاس‌های مورد استفاده آن به شکل زیبایی ظاهر خواهند شد. به این صورت صرفه جویی قابل ملاحظه‌ای در وقت صورت خواهد گرفت.

نتیجه اجرای اسکریپت فوق (پس از نصب) به صورت زیر است:



نکته: نحوه دیباگ کردن اسکریپت‌های گریس مانکی

اگر نیاز به مشاهده مقدار متغیرها در لحظه اجرای اسکریپت داشتید، یکی از راه‌حل‌های موجود استفاده از تابع GM_log مربوط به API گریس مانکی است که خروجی آن‌را در قسمت messages مربوط به error console فایرفاکس می‌توان دید.



نظرات مطالب
نوشتن TagHelperهای سفارشی برای ASP.NET Core
یک نکته‌ی تکمیلی: به روز رسانی یک Tag Helper از طریق Ajax

فرض کنید قسمتی از صفحه را با یک Tag Helper سفارشی ایجاد کرده‌اید. اگر بخواهید یک دکمه‌ی به روز رسانی را هم در اینجا اضافه کنید تا به صورت Ajax ایی این قسمت به روز شود، نیاز است بتوان این تگ هلپر را مجددا تولید کرد و سپس به صورت ()return Content بازگشت داد.
برای اینکار قسمتی که سبب رندر مجدد یک تگ هلپر می‌شود، به صورت زیر قابل پیاده سازی است:
var tagHelper = new MyCustomTagHelper();

var tagHelperContext = new TagHelperContext(
    allAttributes: new TagHelperAttributeList(),
    items: new Dictionary<object, object>(),
    uniqueId: Guid.NewGuid().ToString("N"));
 
var tagHelperOutput = new TagHelperOutput(
    tagName: "div",
    attributes: new TagHelperAttributeList(),
    getChildContentAsync: (useCachedResult, encoder) =>
    {
        var tagHelperContent = new DefaultTagHelperContent();
        tagHelperContent.SetContent(string.Empty);
        return Task.FromResult<TagHelperContent>(tagHelperContent);
    });
 
tagHelper.Process(tagHelperContext, tagHelperOutput);
var content = tagHelperOutput.Content.GetContent();
مطالب
استفاده از Froala WYSIWYG Editor در ASP.NET
چندی قبل، معرفی ادیتور سبک وزن و مناسبی را تحت عنوان RedActor، در این سایت ملاحظه کردید. زمانیکه این‌کار انجام شد، این ادیتور هم رایگان بود و هم سورس آخرین نگارش آن به سادگی در دسترس. بعد از مدتی، هر دو ویژگی یاد شده‌ی RedActor حذف شدند. پس از آن ادیتور مدرن و بسیار مناسب دیگری به نام Froala منتشر شد که هرچند نگارش‌های تجاری هم دارد، اما سورس آخرین نگارش آن برای عموم قابل دریافت است. در ادامه مروری خواهیم داشت بر نحوه‌ی یکپارچه سازی آن با ASP.NET MVC و همچنین ASP.NET Web forms.


دریافت آخرین نگارش Froala WYSIWYG Editor
برای دریافت فایل‌های آخرین نگارش این ادیتور وب می‌توانید به سایت آن، قسمت دریافت فایل‌ها مراجعه نمائید.
http://editor.froala.com/download
و یا به این آدرس مراجعه کنید:
https://github.com/froala/wysiwyg-editor/releases 


ساختار پروژه و نحوه‌ی کپی فایل‌های آن
در هر دو مثالی که فایل‌های آن‌را از انتهای بحث می‌توانید دریافت کنید، این ساختار رعایت شده است:


فایل‌های CSS و فونت‌های آن، در پوشه‌ی Content قرار گرفته‌اند.
فایل‌های اسکریپت و زبان آن (که دارای زبان فارسی هم هست) در پوشه‌ی Scripts کپی شده‌اند.

یک نکته
فایل font-awesome.css را نیاز است کمی اصلاح کنید. مسیر پوشه‌ی فونت‌های آن اکنون با fonts شروع می‌شود.


تنظیمات اولیه

تفاوتی نمی‌کند که از وب فرم‌ها استفاده می‌کنید یا MVC، نحوه‌ی تعریف و افزودن پیش نیازهای این ادیتور به نحو ذیل است:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>

    <link href="Content/font-awesome.css" rel="stylesheet" />
    <link href="Content/froala_editor.css" rel="stylesheet" />

    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/froala_editor.min.js"></script>
    <script src="Scripts/langs/fa.js"></script>
</head>
<body>
    <form id="form1" runat="server">
    </form>
</body>
</html>
دو فایل CSS دارد (آیکن‌های آن و همچنین شیوه نامه‌ی اصلی ادیتور) به همراه سه فایل JS (جی‌کوئری، ادیتور و فایل زبان فارسی آن) که باید در فایل master یا layout سایت اضافه شوند.


استفاده از Froala WYSIWYG Editor در ASP.NET MVC

در ادامه نحوه‌ی فعال سازی ادیتور وب Froala را در یک View برنامه‌های ASP.NET MVC ملاحظه می‌کنید:
@{
    ViewBag.Title = "Index";
}

<style type="text/css">
    /*تنظیم فونت پیش فرض ادیتور*/
    .froala-element {
    }
</style>

@using (Html.BeginForm(actionName: "Index", controllerName: "Home"))
{
    @Html.TextArea(name: "Editor1")
    <input type="submit" value="ارسال" />
}

@section Scripts
{
    <script type="text/javascript">
        $(function () {
            $('#Editor1').editable({
                buttons: ["bold", "italic", "underline", "strikeThrough", "fontFamily",
                    "fontSize", "color", "formatBlock", "align", "insertOrderedList",
                    "insertUnorderedList", "outdent", "indent", "selectAll", "createLink",
                    "insertImage", "insertVideo", "undo", "redo", "html", "save", "inserthorizontalrule"],
                inlineMode: false,
                inverseSkin: true,
                preloaderSrc: '@Url.Content("~/Content/img/preloader.gif")',
                allowedImageTypes: ["jpeg", "jpg", "png"],
                height: 300,
                language: "fa",
                direction: "rtl",
                fontList: ["Tahoma, Geneva", "Arial, Helvetica", "Impact, Charcoal"],
                autosave: true,
                autosaveInterval: 2500,
                saveURL: '@Url.Action("FroalaAutoSave", "Home")',
                saveParams: { postId: "123" },
                spellcheck: true,
                plainPaste: true,
                imageButtons: ["removeImage", "replaceImage", "linkImage"],
                borderColor: '#00008b',
                imageUploadURL: '@Url.Action("FroalaUploadImage", "Home")',
                imageParams: { postId: "123" },
                enableScript: false
            });
        });
    </script>
}
اگر می‌خواهید فونت پیش فرض آن را تنظیم کنید، باید مطابق کدهای ابتدای فایل، ویژگی‌های froala-element را تغییر دهید.
سپس این ادیتور را بر روی المان TextArea قرار گرفته در صفحه، فعال می‌کنیم.
در قسمت مقادیر buttons، تمام حالات ممکن پیش بینی شده‌اند. هر کدام را که نیاز ندارید، حذف کنید.
نحوه‌ی تعریف زبان و راست به چپ بودن این ادیتور را با مقدار دهی پارامترهای language و direction ملاحظه می‌کنید.

پارامترهای autosave، saveURL و saveParams کار تنظیم ارسال خودکار محتوای ادیتور را جهت ذخیره‌ی آن در سرور به عهده دارند. بر اساس مقدار autosaveInterval می‌توان مشخص کرد که هر چند میلی ثانیه یکبار این‌کار باید انجام شود.
        /// <summary>
        /// ذخیره سازی خودکار
        /// </summary>
        [HttpPost]
        [ValidateInput(false)]
        public ActionResult FroalaAutoSave(string body, int? postId) // نام پارامتر بادی را تغییر ندهید
        {
            //todo: save body ...
            return new EmptyResult();
        }
در قسمت سمت سرور هم می‌توان این مقادیر ارسالی را در اکشن متدی که ملاحظه می‌کنید، دریافت کرد.
چون قرار است تگ‌های HTML به سرور ارسال شوند، ویژگی ValidateInput به false تنظیم شده‌است.
saveParams آن، برای مقدار دهی پارامترهای اضافی است که نیاز می‌باشند تا به سرور ارسال شوند. مثلا شماره مطلب جاری نیز به سرور ارسال گردد.
در اینجا نام پارامتری که ارسال می‌گردد، دقیقا مساوی body است. بنابراین آن‌را تغییر ندهید.

پارامترهای imageUploadURL و imageParams برای فعال سازی ذخیره تصاویر آن در سرور کاربرد دارند.
اکشن متد مدیریت کننده‌ی آن به نحو ذیل می‌تواند تعریف شود:
        // todo: مسایل امنیتی آپلود را فراموش نکنید
        /// <summary>
        /// ذخیره سازی تصاویر ارسالی
        /// </summary>
        [HttpPost]
        public ActionResult FroalaUploadImage(HttpPostedFileBase file, int? postId) // نام پارامتر فایل را تغییر ندهید
        {
            var fileName = Path.GetFileName(file.FileName);
            var rootPath = Server.MapPath("~/images/");
            file.SaveAs(Path.Combine(rootPath, fileName));
            return Json(new { link = "images/" + fileName }, JsonRequestBehavior.AllowGet);
        }
در اینجا نام پارامتری که به سرور ارسال می‌گردد، دقیقا معادل file است. بنابراین آن‌را تغییر ندهید.
خروجی آن برای مشخص سازی محل ذخیره سازی تصویر در سرور باید یک خروجی JSON دارای خاصیت و پارامتر link به نحو فوق باشد (این مسیر، یک مسیر نسبی است؛ نسبت به ریشه سایت).
imageParams آن برای مقدار دهی پارامترهای اضافی است که نیاز می‌باشند تا به سرور ارسال شوند. مثلا شماره مطلب جاری نیز به سرور ارسال گردد.


استفاده از Froala WYSIWYG Editor در ASP.NET Web forms
تمام نکاتی که در قسمت تنظیمات ASP.NET MVC در مورد ویژگی‌های سمت کلاینت این ادیتور ذکر شد، در مورد وب فرم‌ها نیز صادق است. فقط قسمت مدیریت سمت سرور آن اندکی تفاوت دارد.
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master"
    ValidateRequest="false"
    EnableEventValidation="false"
    AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="FroalaWebFormsTest.Default" %>

<%--اعتبارسنجی ورودی غیرفعال شده چون باید تگ ارسال شود--%>
<%--همچنین در وب کانفیگ هم تنظیم دیگری نیاز دارد--%>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <%--حالت کلاینت آی دی بهتر است تنظیم شود در اینجا--%>
    <asp:TextBox ID="txtEditor" ClientIDMode="Static"
        runat="server" Height="199px" TextMode="MultiLine" Width="447px"></asp:TextBox>
    <br />
    <asp:Button ID="btnSave" runat="server" OnClick="btnSave_Click" Text="ارسال" />

    <style type="text/css">
        /*تنظیم فونت پیش فرض ادیتور*/
        .froala-element {
        }
    </style>

    <script type="text/javascript">
        $(function () {
            $('#txtEditor').editable({
                buttons: ["bold", "italic", "underline", "strikeThrough", "fontFamily",
                    "fontSize", "color", "formatBlock", "align", "insertOrderedList",
                    "insertUnorderedList", "outdent", "indent", "selectAll", "createLink",
                    "insertImage", "insertVideo", "undo", "redo", "html", "save", "inserthorizontalrule"],
                inlineMode: false,
                inverseSkin: true,
                preloaderSrc: 'Content/img/preloader.gif',
                allowedImageTypes: ["jpeg", "jpg", "png"],
                height: 300,
                language: "fa",
                direction: "rtl",
                fontList: ["Tahoma, Geneva", "Arial, Helvetica", "Impact, Charcoal"],
                autosave: true,
                autosaveInterval: 2500,
                saveURL: 'FroalaHandler.ashx',
                saveParams: { postId: "123" },
                spellcheck: true,
                plainPaste: true,
                imageButtons: ["removeImage", "replaceImage", "linkImage"],
                borderColor: '#00008b',
                imageUploadURL: 'FroalaHandler.ashx',
                imageParams: { postId: "123" },
                enableScript: false
            });
        });
    </script>
</asp:Content>
همانطور که ملاحظه می‌کنید،  ValidateRequest صفحه به false تنظیم شده و همچنین در وب کانفیگ httpRuntime requestValidationMode به نگارش 2 تنظیم گردیده‌است تا بتوان توسط این ادیتور تگ‌های ارسالی را به سرور ارسال کرد.
به علاوه ClientIDMode=Static نیز تنظیم شده‌است، تا بتوان از ID تکست باکس قرار گرفته در صفحه، به سادگی در کدهای سمت کاربر جی‌کوئری استفاده کرد.
اگر دقت کرده باشید، save urlها اینبار به فایل FroalaHandler.ashx اشاره می‌کنند. محتوای این Genric handler را ذیل مشاهده می‌کنید:
using System.IO;
using System.Web;
using System.Web.Script.Serialization;

namespace FroalaWebFormsTest
{
    public class FroalaHandler : IHttpHandler
    {
        //todo: برای اینکارها بهتر است از وب ای پی آی استفاده شود
        //todo: یا دو هندلر مجزا یکی برای تصاویر و دیگری برای ذخیره سازی متن

        public void ProcessRequest(HttpContext context)
        {
            var body = context.Request.Form["body"];
            var postId = context.Request.Form["postId"];
            if (!string.IsNullOrWhiteSpace(body) && !string.IsNullOrWhiteSpace(postId))
            {
                //todo: save changes

                context.Response.ContentType = "text/plain";
                context.Response.Write("");
                context.Response.End();
            }

            var files = context.Request.Files;
            if (files.Keys.Count > 0)
            {
                foreach (string fileKey in files)
                {
                    var file = context.Request.Files[fileKey];
                    if (file == null || file.ContentLength == 0)
                        continue;

                    //todo: در اینجا مسایل امنیتی آپلود فراموش نشود
                    var fileName = Path.GetFileName(file.FileName);
                    var rootPath = context.Server.MapPath("~/images/");
                    file.SaveAs(Path.Combine(rootPath, fileName));


                    var json = new JavaScriptSerializer().Serialize(new { link = "images/" + fileName });
                    // البته اینجا یک فایل بیشتر ارسال نمی‌شود
                    context.Response.ContentType = "text/plain";
                    context.Response.Write(json);
                    context.Response.End();
                }
            }

            context.Response.ContentType = "text/plain";
            context.Response.Write("");
            context.Response.End();
        }

        public bool IsReusable
        {
            get { return false; }
        }
    }
}
در اینجا نحوه‌ی مدیریت سمت سرور auto save و همچنین ارسال تصاویر ادیتور Froala ، ذکر شده‌اند. با استفاده از context.Request.Form می‌توان به عناصر ارسالی به سرور دسترسی پیدا کرد. همچنین توسط context.Request.Files، اگر فایلی ارسال شده بود، ذخیره شده و نهایتا خروجی JSON مدنظر بازگشت داده می‌شود.


یک نکته‌ی امنیتی مهم
<location path="upload">
  <system.webServer>
    <handlers accessPolicy="Read" />
  </system.webServer>
</location>
تنظیم فوق را در web.config سایت، جهت Read only کردن پوشه‌ی ارسال تصاویر، حتما مدنظر داشته باشید. در اینجا فرض شده‌است که پوشه‌ی uploads قرار است قابلیت اجرای فایل‌های پویا را نداشته باشد.


کدهای کامل این مطلب را در ادامه می‌توانید دریافت کنید
Froala-Sample
 
نظرات مطالب
استخراج تمام XPathهای یک محتوای HTMLایی به کمک کتابخانه HtmlAgilityPack
- برای استخراج هر نوع تگ قرار گرفته شده داخل HTML نهایی، میشه از این کتابخانه استفاده کرده. فقط کافی است در switch htmlNode.Name  مطلب فوق، scriptها را تحت نظر قرار داد.
- برای اجرای کدهای جاوا اسکریپت در دات نت، یک سری موتور ویژه برای اینکار هست.
مطالب
معرفی Selector های CSS - قسمت 1
1- .class
این Selector تگ‌هایی را انتخاب می‌نماید که عضو یک کلاس خاص باشند.
<style>
    .first{ color: red}
    .content{color:blue}
</style>
<div class="first">Text 1</div>
<div>Text 2</div>
<p class="first">Text 3</p>
<div class="content">Text 4</div>
در مثال فوق Text 1 و Text 3 به رنگ قرمز و Text 4 به رنگ آبی نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes .class   1

2- #id
این Selector تگی را انتخاب می‌کند که دارای یک id خاص می‌باشد.
<style>
    #pass {
        color: red;
    }        
</style>
<input type="password" id="pass"/>
در مثال فوق محتوای کادر رمز عبور به رنگ قرمز نمایش می‌یابد.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes #id   1

3- E
جهت انتخاب تگ‌ها براساس عنوان آنها استفاده می‌شود.
<style>
    div {
        color: red;
    }
</style>
<div>Text 1</div>
<p>Text 2</p>
<div>Text 3</div>
در مثال فوق Text 1 و Text 3 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes  1

4- *
این Selector تمامی تگ‌ها را انتخاب می‌نماید
<style>
    * {
        color: red;
    }
</style>
<div>Text 1</div>
<input type="text" value="Text 2"/>
<select>
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
    <option>Option 4</option>
</select>
در مثال فوق رنگ متن تمامی تگهای فوق قرمز خواهد شد.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0  2

5- S1  S2
تمامی المنت‌های S2 که فرزند S1 می‌باشند، انتخاب خواهند شد.
<style>
    div span {
        color:red
    }
</style>
<div>
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>
        <span>Text 3</span>
        <div>Text 4</div>
    </p>
    <span>Text 5</span>
</div>
در مثال فوق Text 2، Text 3 و Text 5 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes S1   S2  1

6- S1>S2
تمامی المنت‌های S2 که فرزند مستقیم S1 می‌باشند، انتخاب خواهند شد. 
<style>
    div>span {
        color:red
    }
</style>
<div>
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>
        <span>Text 3</span>
        <div>Text 4</div>
    </p>
    <span>Text 5</span>
</div>
در مثال فوق Text 2 و Text 5 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  7.0 Yes   Yes S1>S2  2 

7- S1+S2
تمامی المنت‌های S2 که هم تراز S1 و دقیقا بعد از S1 قرار گرفته اند را انتخاب می‌نماید.
<style>
    h1+p {
        color: red;
    }
</style>
<div>
    <h1>Text 1</h1>
    <p>Text 2</p>
    <div>Text 3</div>
    <p>Text 4</p>
    <h1>
        <p>Text 5</p>
    </h1>
    <p>Text 6</p>
</div>
در مثال فوق Text 2 و Text 6 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها: 

 Selector نسخه CSS
 Yes  Yes  7.0 Yes   Yes S1+S2  2 

8- S1~S2
تمامی المنت‌های S2 که هم تراز S1 می‌باشند و بعد از S1 قرار گرفته اند را انتخاب می‌نماید.
<style>
    div ~ p {
        color: red;
    }
</style>
<div>
    <h1>Text 1</h1>
    <p>Text 2</p>
    <div>Text 3</div>
    <p>Text 4</p>
    <h1>
        <p>Text 5</p>
    </h1>
    <p>Text 6</p>
</div>
در مثال فوق Text 4 و Text 6 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 S1~S2  3

9- S1!>S2
این Selector المنت‌های S1 را انتخاب می‌کند که والد مستقیم S2 می‌باشند.
<style>
    div!>span {
            border: 1px solid red;
        }
</style>
<div>
    <h1>Text 1</h1>
    <div>
        <span>Text 2</span>
        <h1>Text 3</h1>
        <p>Text 4</p>
    </div>
    <div>Text 5</div>
</div>
در مثال فوق کادری قرمز رنگ را دور آن div که شامل محتوای Text 2، Text 3 و Text 4 می‌باشد، ترسیم خواهد نمود.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No  No No
No
No S1!>S2  4

 10- S1 /attribute/ S2
تمامی المنت‌های S2 انتخاب می‌شوند که توسط یک attribute از S1 به id المنت S2 ارجاع داده است.
<style>
    label /for/ input {
        color: red;
    }
</style>
<label for="user">User Name:</label>
<input type="text" id="user"/>
<label>Password:</label>
<input type="password" id="pass"/>
در مثال فوق، اولین تگ input که id آن user می‌باشد و توسط تگ label و با استفاده از ویژگی for (به id تگ input) ارجاع داده شده است، انتخاب و محتوای آن قرمز می‌شود.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No  No No
No
No S1 /attribute/ S2  4
 
مطالب دوره‌ها
پیاده سازی امتیاز دهی ستاره‌ای به مطالب به کمک jQuery در ASP.NET MVC
در این قسمت قصد داریم با نحوه پیاده سازی امتیاز دهی ستاره‌ای به مطالب، که نمونه‌ای از آن‌را در سایت جاری در قسمت‌های مختلف آن مشاهده می‌کنید، آشنا شویم.


مدل برنامه

در ابتدای کار نیاز است تا ساختاری را جهت ارائه لیستی از مطالب که دارای گزینه امتیاز دهی می‌باشند، تهیه کنیم:
namespace jQueryMvcSample03.Models
{
    public class BlogPost
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }

        /// <summary>
        /// اطلاعات رای گیری یک مطلب به صورت یک خاصیت تو در تو یا پیچیده
        /// </summary>
        public Rating Rating { set; get; }

        public BlogPost()
        {
            Rating = new Rating();
        }
    }
}

namespace jQueryMvcSample03.Models
{
    //[ComplexType]
    public class Rating
    {
        public double? TotalRating { get; set; }
        public int? TotalRaters { get; set; }
        public double? AverageRating { get; set; }
    }
}
اگر با EF Code first آشنا باشید، خاصیت Rating تعریف شده در اینجا می‌تواند از نوع ComplexType تعریف شود که شامل جمع امتیازهای داده شده، تعداد کل رای دهنده‌ها و همچنین میانگین امتیازهای حاصل است.


منبع داده فرضی برنامه

using System.Collections.Generic;
using System.Linq;
using jQueryMvcSample03.Models;

namespace jQueryMvcSample03.DataSource
{
    /// <summary>
    /// منبع داده فرضی
    /// </summary>
    public static class BlogPostDataSource
    {
        private static IList<BlogPost> _cachedItems;
        /// <summary>
        /// با توجه به استاتیک بودن سازنده کلاس، تهیه کش، پیش از سایر فراخوانی‌ها صورت خواهد گرفت
        /// باید دقت داشت که این فقط یک مثال است و چنین کشی به معنای
        /// تهیه یک لیست برای تمام کاربران سایت است
        /// </summary>
        static BlogPostDataSource()
        {
            _cachedItems = createBlogPostsInMemoryDataSource();
        }

        /// <summary>
        /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است
        /// </summary>        
        private static IList<BlogPost> createBlogPostsInMemoryDataSource()
        {
            var results = new List<BlogPost>();
            for (int i = 1; i < 30; i++)
            {
                results.Add(new BlogPost { Id = i, Title = "عنوان " + i, Body = "متن ... متن ... متن " + i, Rating = new Rating { TotalRaters = i + 1, AverageRating = 3.5 } });
            }
            return results;
        }

        /// <summary>
        /// پارامترهای شماره صفحه و تعداد رکورد به ازای یک صفحه برای صفحه بندی نیاز هستند
        /// شماره صفحه از یک شروع می‌شود
        /// </summary>
        public static IList<BlogPost> GetLatestBlogPosts(int pageNumber, int recordsPerPage = 4)
        {
            var skipRecords = pageNumber * recordsPerPage;
            return _cachedItems
                        .OrderByDescending(x => x.Id)
                        .Skip(skipRecords)
                        .Take(recordsPerPage)
                        .ToList();
        }
    }
}
در این مثال نیز از یک منبع داده فرضی تشکیل شده در حافظه استفاده خواهیم کرد تا امکان اجرای پروژه پیوستی را بدون نیاز به بانک اطلاعاتی خاصی و بدون نیاز به مقدمات برپایی آن، به سادگی داشته باشید.
در این منبع داده ابتدا لیستی از مطالب تهیه شده و سپس کش می‌شوند. در ادامه توسط متد GetLatestBlogPosts بازه‌ای از این اطلاعات قابل بازیابی خواهند بود که برای استفاده در حالات صفحه بندی اطلاعات بهینه سازی شده است.


آشنایی با طراحی افزونه jQuery Star Rating

افزودن CSS نمایش امتیازها در ذیل هر مطلب

/* star rating system */
.post_rating
{
direction: ltr;
}
.rating
{
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
display: inline-block;
width: 8px;
height: 16px;
}
.rating.stars
{
background-image: url('Images/star_rating.png');
}
.rating.stars.active
{
cursor: pointer;
}
.star-left_off
{
background-position: -0px -0px;
}
.star-left_on
{
background-position: -16px -0px;
}
.star-right_off
{
background-position: -8px -0px;
}
.star-right_on
{
background-position: -24px -0px;
}
برای نمایش ستاره‌ها و کار با تصویر Images/star_rating.png (که در پروژه پیوست قرار دارد) ابتدا نیاز است CSS فوق را به پروژه خود اضافه نمائید.

افزودن افزونه jQuery Star rating

// <![CDATA[
(function ($) {
    $.fn.StarRating = function (options) {
        var defaults = {            
            ratingStarsSpan: '.rating.stars',
            postInfoUrl: '/',
            loginUrl: '/login',
            errorHandler: null,
            completeHandler: null,
            onlyOneTimeHandler: null
        };
        var options = $.extend(defaults, options);

        return this.each(function () {
            var ratingStars = $(this);

            $(ratingStars).unbind('mouseover');
            $(ratingStars).mouseover(function () {
                var span = $(this).parent("span");
                var newRating = $(this).attr("value");
                setRating(span, newRating);
            });

            $(ratingStars).unbind('mouseout');
            $(ratingStars).mouseout(function () {
                var span = $(this).parent("span");
                var rating = span.attr("rating");
                setRating(span, rating);
            });

            $(ratingStars).unbind('click');
            $(ratingStars).click(function () {
                var span = $(this).parent("span");
                var newRating = $(this).attr("value");
                var text = span.children("span");
                var pID = span.attr("post");
                var type = span.attr("sectiontype");
                postData({ postID: pID, rating: newRating, sectionType: type });
                span.attr("rating", newRating);
                setRating(span, newRating);
            });

            function setRating(span, rating) {
                span.find(options.ratingStarsSpan).each(function () {
                    var value = parseFloat($(this).attr("value"));
                    var imgSrc = $(this).attr("class");
                    if (value <= rating)
                        $(this).attr("class", imgSrc.replace("_off", "_on"));
                    else
                        $(this).attr("class", imgSrc.replace("_on", "_off"));
                });
            }

            function postData(dataJsonArray) {
                $.ajax({
                    type: "POST",
                    url: options.postInfoUrl,
                    data: JSON.stringify(dataJsonArray),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    complete: function (xhr, status) {
                        var data = xhr.responseText;
                        if (xhr.status == 403) {
                            window.location = options.loginUrl;
                        }
                        else if (status === 'error' || !data) {
                            if (options.errorHandler)
                                options.errorHandler(this);
                        }
                        else if (data == "nok") {
                            if (options.onlyOneTimeHandler)
                                options.onlyOneTimeHandler(this);
                        }
                        else {
                            if (options.completeHandler)
                                options.completeHandler(this);
                        }
                    }
                });
            }
        });
    };
})(jQuery);
// ]]>
اطلاعات فوق، فایل jquery.StarRating.js را تشکیل می‌دهند که باید به پروژه اضافه گردند.
کاری که این افزونه انجام می‌دهد ردیابی حرکت ماوس بر روی ستاره‌های نمایش داده شده و سپس ارسال سه پارامتر ذیل به اکشن متدی که توسط پارامتر postInfoUrl مشخص می‌گردد، پس از کلیک کاربر می‌باشد:
 { postID: pID, rating: newRating, sectionType: type }
همانطور که ملاحظه می‌کنید به ازای هر قطعه رای گیری که به صفحه اضافه می‌شود، Id مطلب، رای داده شده و نام قسمت جاری، به اکشن متدی خاص ارسال خواهند گردید. sectionType از این جهت اضافه گردیده است تا بتوانید با بیش از یک جدول کار کنید و از این افزونه در قسمت‌های مختلف سایت به سادگی بتوانید استفاده نمائید.
در اینجا از errorHandler برای نمایش خطاها، از completeHandler برای نمایش تشکر به کاربر و از onlyOneTimeHandler برای نمایش اخطار مثلا «یکبار بیشتر مجاز نیستید به ازای یک مطلب رای دهید»، می‌توان استفاده کرد.

بنابراین تا اینجا فایل layout برنامه تقریبا چنین مداخلی را خواهد داشت:
<head>
    <title>@ViewBag.Title</title>    
    <link href="@Url.Content("Content/starRating.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.StarRating.js")" type="text/javascript"></script>
    @RenderSection("JavaScript", required: false)
</head>

طراحی یک HTML helper برای نمایش ستاره‌های امتیاز دهی

ابتدا پوشه استاندارد app_code را به پروژه اضافه کرده و سپس فایلی را به نام StarRatingHelper.cshtml، با محتوای ذیل به آن اضافه نمائید:
@using System.Globalization
@helper AddStarRating(int postId,
                      double? average = 0, int? postRatingsCount = 0, string type = "BlogPost",
                      string tooltip = "لطفا جهت رای دادن کلیک نمائید")
    {
        string actIt = "active ";
        if (!average.HasValue) { average = 0; }
        if (!postRatingsCount.HasValue) { postRatingsCount = 0; }
    
    <span class='postRating' rating='@average' post='@postId' title='@tooltip' sectiontype='@type'>
        @for (double i = .5; i <= 5.0; i = i + .5)
        {
            string left;
            if (i <= average)
            {
                left = (i * 2) % 2 == 1 ? "left_on" : "right_on";
            }
            else
            {
                left = (i * 2) % 2 == 1 ? "left_off" : "right_off";
            }
            <span class='rating stars @(actIt)star-@left' value='@i'></span>
        }
        &nbsp;
        @if (postRatingsCount > 0)
        {
            var ratingInfo = string.Format(CultureInfo.InvariantCulture, "امتیاز {0:0.00} از 5 توسط {1} نفر", average, postRatingsCount);
            <span>@ratingInfo</span>                
        }
        else
        {
            <span></span>
        }
    </span>
}
از این Html helper برای تشکیل ساختار نمایش قطعه امتیاز دهی به یک مطلب استفاده خواهیم کرد که توسط افزونه جی‌کوئری فوق ردیابی می‌شود.


کنترلر ذخیره سازی اطلاعات دریافتی برنامه

using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample03.DataSource;
using jQueryMvcSample03.Security;

namespace jQueryMvcSample03.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var postsList = BlogPostDataSource.GetLatestBlogPosts(pageNumber: 0);
            return View(postsList); //نمایش صفحه اصلی
        }


        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult SaveRatings(int? postId, double? rating, string sectionType)
        {
            if (postId == null || rating == null || string.IsNullOrWhiteSpace(sectionType))
                return Content(null); //اعلام بروز خطا

            if (!this.HttpContext.CanUserVoteBasedOnCookies(postId.Value, sectionType))
                return Content("nok"); //اعلام فقط یکبار مجاز هستید رای دهید

            switch (sectionType) //قسمت‌های مختلف سایت که در جداول مختلفی قرار دارند نیز می‌توانند گزینه امتیاز دهی داشته باشند
            {
                case "BlogPost":
                    //الان شماره مطلب و رای ارسالی را داریم که می‌توان نسبت به ذخیره آن اقدام کرد
                    //مثلا
                    //_blogPostsService.SaveRating(postId.Value, rating.Value);
                    break;

                //... سایر قسمت‌های دیگر سایت

                default:
                    return Content(null); //اعلام بروز خطا
            }

            return Content("ok"); //اعلام موفقیت آمیز بودن ثبت اطلاعات
        }

        [HttpGet]
        public ActionResult Post(int? id)
        {
            if (id == null)
                return Redirect("/");

            //todo: show the content here
            return Content("Post " + id.Value);
        }
    }
}
در اینجا کنترلری را که کار پردازش کلیک کاربر را بر روی امتیازی خاص انجام می‌دهد، ملاحظه می‌کنید.
امضای اکشن متد SaveRatings دقیقا بر اساس سه پارامتر ارسالی توسط jquery.StarRating.js که پیشتر توضیح داده شد، تعیین گردیده است. در این متد ابتدا بررسی می‌شود که آیا اطلاعاتی دریافت شده است یا خیر. اگر خیر، null را بازگشت خواهد داد. سپس توسط متد CanUserVoteBasedOnCookies بررسی می‌شود که آیا کاربر می‌تواند (خصوصا مجددا) رای دهد یا خیر. این افزونه برای رای دهی کاربران وارد نشده به سیستم نیز مناسب است. به همین جهت از کوکی‌ها برای ثبت اطلاعات رای دادن کاربران استفاده گردیده است. پیاده سازی متد CanUserVoteBasedOnCookies را در ادامه ملاحظه خواهید نمود.
در ادامه در متد SaveRatings، یک switch تشکیل شده است تا بر اساس نام قسمت مرتبط به رای گیری، اطلاعات را بتوان به سرویس خاصی در برنامه هدایت کرد. مثلا اطلاعات قسمت مطالب به سرویس مطالب و قسمت نظرات به سرویس نظرات هدایت شوند.


متدهایی برای کار با کوکی‌ها در ASP.NET MVC

using System;
using System.Web;

namespace jQueryMvcSample03.Security
{
    public static class CookieHelper
    {
        public static bool CanUserVoteBasedOnCookies(this HttpContextBase httpContext, int postId, string sectionType)
        {
            string key = sectionType + "-" + postId;
            var value = httpContext.GetCookieValue(key);
            if (string.IsNullOrWhiteSpace(value))
            {
                httpContext.AddCookie(key, key);
                return true;
            }
            return false;
        }

        public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value)
        {
            httpContextBase.AddCookie(cookieName, value, DateTime.Now.AddDays(30));
        }

        public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value, DateTime expires)
        {
            var cookie = new HttpCookie(cookieName)
            {
                Expires = expires,
                Value = httpContextBase.Server.UrlEncode(value) // For Cookies and Unicode characters
            };
            httpContextBase.Response.Cookies.Add(cookie);
        }

        public static string GetCookieValue(this HttpContextBase httpContext, string cookieName)
        {
            var cookie = httpContext.Request.Cookies[cookieName];
            if (cookie == null)
                return string.Empty; //cookie doesn't exist

            // For Cookies and Unicode characters
            return httpContext.Server.UrlDecode(cookie.Value);
        }
    }
}
در اینجا یک سری متد الحاقی را ملاحظه می‌کنید که برای ثبت اطلاعات رای داده شده یک کاربر بر اساس Id مطلب و نام قسمت متناظر با آن در یک کوکی طراحی شده‌اند. بدیهی است اگر تمام قسمت‌های برنامه شما محافظت شده هستند و کاربران حتما نیاز است ابتدا به سیستم لاگین نمایند، می‌توانید این قسمت را حذف کرده و اطلاعات postId و SectionType را به ازای هر کاربر، جداگانه در بانک اطلاعاتی ثبت و بازیابی نمائید (دقیق‌ترین حالت ممکن؛ البته برای سیستمی بسته که حتما تمام قسمت‌های آن نیاز به اعتبار سنجی دارند).


پیشنهادی در مورد نحوه ذخیره سازی اطلاعات دریافتی

using jQueryMvcSample03.Models;

namespace jQueryMvcSample03.DataSource
{
    public interface IBlogPostsService
    {
        void SaveRating(int postId, double rating);
    }

    public class SampleService : IBlogPostsService
    {
        /// <summary>
        /// یک نمونه از متد ذخیره سازی اطلاعات پیشنهادی
        /// فقط برای ایده گرفتن
        /// بدیهی است محل قرارگیری اصلی آن در لایه سرویس برنامه شما خواهد بود
        /// </summary>
        public void SaveRating(int postId, double rating)
        {
            BlogPost post = null;
            //post = _blogCtx.Find(postId); // بر اساس شماره مطلب، مطلب یافت شده و فیلدهای آن تنظیم می‌شوند
            if (post == null) return;

            if (!post.Rating.TotalRaters.HasValue) post.Rating.TotalRaters = 0;
            if (!post.Rating.TotalRating.HasValue) post.Rating.TotalRating = 0;
            if (!post.Rating.AverageRating.HasValue) post.Rating.AverageRating = 0;

            post.Rating.TotalRaters++;
            post.Rating.TotalRating += rating;
            post.Rating.AverageRating = post.Rating.TotalRating / post.Rating.TotalRaters;

            // todo: call save changes at the end.
        }
    }
}
همانطور که عنوان شد، سه داده Id مطلب، رای داده شده و نام قسمت متناظر به اکشن متد ارسال می‌شود. از نام قسمت، برای انتخاب سرویس ذخیره سازی اطلاعات استفاده خواهیم کرد. این سرویس می‌تواند شامل متدی به نام SaveRating، همانند کدهای فوق باشد که Id مطلب و عدد رای حاصل به آن ارسال می‌گردند. ابتدا بر اساس این Id، مطلب متناظر یافت شده و سپس اطلاعات Rating آن به روز خواهد شد. در پایان هم ذخیره سازی اطلاعات باید صورت گیرد.



Viewهای برنامه

قسمت پایانی کار ما در اینجا تهیه دو View است:
الف) یک Partial view که لیست مطالب را به همراه گزینه رای دهی به آن‌ها رندر می‌کند.
ب) View کاملی که از این Partial View استفاده کرده و همچنین افزونه jquery.StarRating.js را فراخوانی می‌کند.
@using System.Text.RegularExpressions
@model IList<jQueryMvcSample03.Models.BlogPost>
<ul>
    @foreach (var item in Model)
    {
        <li>
            <fieldset>
            <legend>مطلب @item.Id</legend>
                <h5>
                    @Html.ActionLink(linkText: item.Title,
                                 actionName: "Post",
                                 controllerName: "Home",
                                 routeValues: new { id = item.Id },
                                 htmlAttributes: null)
                </h5>
                @item.Body
                <div class="post_rating">
                    @Html.Raw(Regex.Replace(@StarRatingHelper.AddStarRating(item.Id, item.Rating.AverageRating, item.Rating.TotalRaters, "BlogPost").ToHtmlString(), @">\s+<", "><"))
                </div>
            </fieldset>
        </li>
    }
</ul>
کدهای _ItemsList.cshtml را در اینجا ملاحظه می‌کند که در آن نحوه فراخوانی متد کمکی StarRatingHelper.AddStarRating ذکر شده است.
اگر به کدهای آن دقت کنید از Regex.Replace برای حذف فاصله‌های خالی و خطوط جدید بین تگ‌ها استفاده گردیده است. اگر اینکار انجام نشود، نیمه‌های ستاره‌های نمایش داده شده، با فاصله از یکدیگر رندر می‌شوند که صورت خوشایندی ندارد.

و نهایتا View ایی که از این اطلاعات استفاده می‌کنید ساختار زیر را خواهد داشت:
@model IList<jQueryMvcSample03.Models.BlogPost>
@{
    ViewBag.Title = "Index";
    var postInfoUrl = Url.Action(actionName: "SaveRatings", controllerName: "Home");
}
<h2>
    سیستم امتیاز دهی</h2>
@{ Html.RenderPartial("_ItemsList", Model); }
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $(".rating.stars.active").StarRating({
                ratingStarsSpan: '.rating.stars',
                postInfoUrl: '@postInfoUrl',
                loginUrl: '/login',
                errorHandler: function () {
                    alert('خطایی رخ داده است');
                },
                completeHandler: function () {
                    alert('با تشکر! رای شما با موفقیت ثبت شد');
                },
                onlyOneTimeHandler: function () {
                    alert('فقط یکبار می‌توانید به ازای هر مطلب رای دهید');
                }
            });
        });
    </script>
}
در این View لیستی از مطالب دریافت و به partial view طراحی شده برای نمایش ارسال می‌شود. سپس افزونه StarRating نیز تنظیم و به صفحه اضافه خواهد گردید. نکته مهم آن تعیین صحیح اکشن متدی است که قرار است اطلاعات را دریافت کند و نحوه مقدار دهی آن‌را توسط متغیر postInfoUrl مشاهده می‌کنید.

دریافت کدها و پروژه کامل این قسمت
jQueryMvcSample03.zip