نظرات مطالب
استفاده از MediaWiki بهترین روش نگهداری یادداشت‌های شخصی خصوصا برای برنامه‌نویس‌ها
من کلا از OneNote در این زمینه‌ها استفاده گسترده‌ای دارم. از چند بابت برام خیلی کاربردی بوده:
- پشتیبانی کامل فارسی و ویرایشگری بی‌نقص‌اش
- همیشه در دسترس است و نیاز به ذخیره‌سازی ندارد
- افزونه‌های کلیپ کردن مطلب هایت‌لایت شده و ارسال مستقیم اون از مرورگر به وان‌نت
- ایجاد صفحات همراه با زیرشاخه درست کردن به نحوی که از نظر منطقی و زیبایی عالی عمل میکنه.
و ممنون از شما بابت معرفی این روش. حتما امتحانش میکنم. امیدوارم از این دست مطالب پویا و سازنده بیشتر باشه. هرکس به‌قدر بضاعت خودش. مخصوصا در فضایی که اینجارو اینقدر پرشور و هیجان کرده.
مطالب
استفاده از modal dialogs مجموعه Twitter Bootstrap برای گرفتن تائید از کاربر
یکی دیگر از اجزای تعاملی Twitter Bootstrap صفحات modal هستند. صفحات modal بر روی صفحه جاری ظاهر شده و کنترل آن‌را در دست می‌گیرند و تا زمانیکه این صفحه ویژه بسته نشود، امکان استفاده از صفحه زیرین، وجود نخواهد داشت. برای استفاده از این امکان ویژه، ابتدا باید یک لینک یا دکمه‌ای، جهت فراخوانی اسکریپت‌های صفحات modal در صفحه تدارک دیده شود.
برای طراحی یک صفحه modal چهار div باید اضافه شوند. بیرونی‌ترین div باید دارای کلاس modal مجموعه Bootstrap باشد. می‌توان به کلاس modal در اینجا کلاس‌های hide fade را هم برای نمونه اضافه کرد. در این حالت، نمایش و بسته شدن صفحه modal به همراه پویانمایی ویژه‌ای خواهد بود.
داخل این div، سه div با کلاس‌های modal-header برای نمایش هدر، modal-body برای نمایش محتوایی در این صفحه modal و modal-footer برای تدارک محتوای footer این صفحه، قرار خواهند گرفت.
در این بین هر لینکی با ویژگی data-dismiss=modal، سبب بسته شدن خودکار صفحه باز شده خواهد شد.

افزونه bootstrapModalConfirm

اگر نکات یاد شده را بخواهیم کپسوله کنیم، می‌توان یک افزونه جدید جی‌کوئری را با نام فایل jquery.bootstrap-modal-confirm.js برای این منظور تدارک دید:
// <![CDATA[
(function ($) {
    $.bootstrapModalConfirm = function (options) {
        var defaults = {
            caption: 'تائید عملیات',
            body: 'آیا عملیات درخواستی اجرا شود؟',
            onConfirm: null,
            confirmText: 'تائید',
            closeText: 'انصراف'
        };
        var options = $.extend(defaults, options);

        var confirmContainer = "#confirmContainer";
        var html = '<div class="modal hide fade" id="confirmContainer">' +
                   '<div class="modal-header">' +
                        '<a class="close" data-dismiss="modal">&times;</a>'
                        + '<h5>' + options.caption + '</h5></div>' +
                   '<div class="modal-body">'
                        + options.body + '</div>' +
                   '<div class="modal-footer">'
                        + '<a href="#" class="btn btn-success" id="confirmBtn">' + options.confirmText + '</a>'
                    + '<a href="#" class="btn" data-dismiss="modal">' + options.closeText + '</a></div></div>';

        $(confirmContainer).remove();
        $(html).appendTo('body');
        $(confirmContainer).modal('show');

        $('#confirmBtn', confirmContainer).click(function () {
            if (options.onConfirm)
                options.onConfirm();
            $(confirmContainer).modal('hide');           
        });
    };
})(jQuery);
// ]]>
در اینجا به صورت پویا div یک صفحه modal ایجاد و دو دکمه تائید و لغو به همراه نمایش یک هدر و همچنین محتوایی به کاربر به صفحه اضافه می‌شود. سپس افزونه modal در حالت show، روی این div فراخوانی می‌گردد. در اینجا اگر کاربر بر روی دکمه تائید کلیک کرد، یک callback به نام onConfirm فراخوانی می‌گردد.


مثالی از استفاده از افزونه bootstrapModalConfirm

@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<a href="#" class="btn btn-danger" id="deleteBtn">حذف رکورد</a>

@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#deleteBtn").click(function (e) {
                e.preventDefault(); //می‌خواهیم لینک به صورت معمول عمل نکند
                $.bootstrapModalConfirm({
                                    caption: 'تائید عملیات',
                                    body: 'آیا عملیات درخواستی اجرا شود؟',
                                    onConfirm: function () {
                                        alert('در حال انجام عملیات'); 
                                    },
                                    confirmText: 'تائید',
                                    closeText: 'انصراف'
                });
            });
        });
    </script>
}
در مثال فوق، اگر کاربر بر روی لینک حذف رکورد، که به صورت یک دکمه مزین شده است کلیک کند، در صورت تائید، قسمت onConfirm اجرا خواهد شد.


مطالب
استفاده از Twitter Bootstrap در کارهای روزمره طراحی وب
پس از آشنایی مقدماتی با Twitter Bootstrap، در این قسمت قصد داریم تا با ویژگی‌هایی از آن آشنا شویم که در کارهای رومزه طراحی وب بسیار مورد استفاده هستند؛ مانند تایپوگرافی، جداول، فرم‌ها، دکمه‌ها، تصاویر و آیکون‌ها.

تایپوگرافی
هدف از تایپوگرافی، چیدمان متن به نحوی است که واضح، خوانا و مشخص باشد؛ همچنین مباحث زیبایی ارائه را نیز به آن اضافه کنید. برای مثال تنظیم فاصله بین حروف و کلمات، فاصله بین خطوط و یا رعایت یک سری نسبت‌های ویژه مانند نسبت طلایی جهت دعوت خواننده به مطالعه مطالب، بجای فراری دادن او، در مباحث تایپوگرافی رعایت می‌شوند. خوشبختانه Twitter Bootstrap به همراه یک سری تنظیمات تایپوگرافی پیش فرض است که در ادامه آن‌ها را مرور خواهیم کرد.
پیش فرض‌های ابتدایی آن‌را مانند قلم با اندازه 14px، قلم پیش فرض Helvetica، فاصله بین خطوط و رنگ متن را در فایل bootstrap.css می‌توانید مشاهده کنید:
body {
    margin: 0;"Helvetica Neue", Helvetica, Arial, sans-serif;
    color: #333333;
    background-color: #ffffff;
}
در اینجا اثر تنظیمات bootstrap را بر روی تگ‌های h1 تا h6 ملاحظه می‌کنید:
 

 
        <div class="row-fluid">
            <div class="span12">
                <h1>
                    سرتیتر 1
                </h1>
                <h2>
                    سرتیتر 2
                </h2>
                <h3>
                    سرتیتر 3
                </h3>
                <h4>
                    سرتیتر 4
                </h4>
                <h5>
                    سرتیتر 5
                </h5>
                <h6>
                    سرتیتر 6
                </h6>
            </div>
        </div>

همچنین اگر نیاز باشد تا نسبت به متنی جلب توجه خواننده را جلب کرد می‌توان از کلاس lead استفاده نمود. به علاوه bootstrap پیش فرض‌هایی را به المان‌های small، strong و em نیز اعمال می‌کند:
        <div class="row-fluid">
            <div class="span12">
                <p class="lead">
                    تیتر</p>
                <p>
                    متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن
                    متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن
                    متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن
                    متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن
                </p>
                <small>اندازه کوچک</small> <strong>متن ضخیم</strong> <em>متن ایتالیک</em>
            </div>
        </div>

روش دیگر جلب توجه به یک متن در bootstrap، استفاده از کلاس‌های text مانند text-error و امثال آن می‌باشد:

 
 
        <div class="row-fluid">
            <div class="span12">
                <p class="muted">
                    متن غیرفعال</p>
                <p class="text-warning">
                    نمایش اخطار به کاربر</p>
                <p class="text-error">
                    نمایش خطا به کاربر</p>
                <p class="text-info">
                    نمایش اطلاعات به کاربر</p>
                <p class="text-success">
                    نمایش موفقیت آمیز بودن عملیات</p>
            </div>
        </div>

bootstrap تنظیماتی را به المان abbreviation موجود در HTML 5 نیز اعمال می‌کند. در این حالت کاربر با نزدیک ساختن اشاره‌گر ماوس به متن مشخص شده، یک tooltip توضیح دهنده نیز ظاهر خواهد شد:

 
 
        <div class="row-fluid">
            <div class="span12">
                <p>
                    My
                    <abbr title="منظور واحد پردازش مرکزی است">
                        CPU</abbr>
                    has N Cores.
                </p>
            </div>
        </div>

در bootstrap المان آدرس نیز شیوه نامه خاص خودش را داشته و به صورت یک قطعه خاص ظاهر می‌شود:
 

        <div class="row-fluid">
            <div class="span12">
                <address>
                    وحید نصیری
                    <br />
                    ایران، تهران
                    <br />
                    <abbr title="Phone">
                        P:</abbr>
                    12345678
                    <br />
                    <abbr title="Cell">
                        C:</abbr>
                    12345678
                </address>
            </div>
        </div>

 
در اینجا تاثیر bootstrap را بر روی المان blockquote ملاحظه می‌کنید؛ همچنین به همراه المان  citeبرای ذکر ماخذ نقل قول ذکر شده:

 
        <div class="row-fluid">
            <div class="span12">
                <blockquote>
                    <p>
                        جهت نمایش نقل قول
                    </p>
                    <small><cite title="کاربر شماره 2">از شخصی</cite> </small>
                </blockquote>
            </div>
        </div>

 
در bootstrap برای حذف بولت‌های کنار یک لیست مرتب، فقط کافی است از کلاس unstyled استفاده شود:
 

        <div class="row-fluid">
            <div class="span6">
                <ul class="unstyled">
                    <li>یک </li>
                    <li>دو </li>
                    <li>سه </li>
                </ul>
            </div>
            <div class="span6">
                <ol>
                    <li>یک </li>
                    <li>دو </li>
                    <li>سه </li>
                </ol>
            </div>
        </div>

 
به علاوه امکان تعریف دو نوع خاص از definition list نیست وجود دارد (المان dl در اینجا). در حالت عادی، ابتدا عنوان مشخص شده با dt یا definition term به صورت ضخیم ظاهر می‌شود؛ به همراه توضیحات ارائه شده در المان dd آن در سطر بعدی. اگر از کلاس dl-horizontal استفاده شود، همانند تصویر ذیل، عنوان در کنار توضیحات در یک سطر قرار خواهد گرفت:
 

        <div class="row-fluid">
            <div class="span12">
                <dl class="dl-horizontal">
                    <dt>عنوان</dt>
                    <dd>
                        توضیحات بلند در اینجا</dd>
                </dl>
            </div>
        </div>

 
در bootstrap امکان اعمال شیوه نامه ابتدایی به کدهای برنامه‌ها نیز وجود دارد. اگر از تگ code استفاده شود، فرض بر این است که قرار است اطلاعاتی را در یک سطر نمایش دهید. اگر اطلاعات کدها، بیشتر از یک سطر است، می‌توان از تگ pre استفاده کرد. در اینجا با اعمال کلاس pre-scrollable به تگ pre، به صورت خودکار یک اسکرول عمودی به قطعه کدها اعمال خواهد شد:
 

 
        <div class="row-fluid">
            <div class="span6">
                <code>inline code</code>
            </div>
            <div class="span6">
                <pre class="pre-scrollable">
                code
                code
                code
                </pre>
            </div>
        </div>



استفاده از جداول و تاثیر bootstrap بر آن‌ها

در ادامه کدهای یک جدول متداول را که مزین شده‌است به کلاس‌های bootstrap ملاحظه می‌کنید:
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span12">
                <table 
class="table table-striped table-hover table-bordered table-condensed">
                    <caption>
                        عنوانی خاص در اینجا</caption>
                    <thead>
                        <tr>
                            <th>
                                ستون یک
                            </th>
                            <th>
                                ستون دو
                            </th>
                            <th>
                                ستون سه
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr class="error">
                            <td>
                                1 متن یک
                            </td>
                            <td>
                                1 متن دو
                            </td>
                            <td>
                                1 متن سه
                            </td>
                        </tr>
                        <tr>
                            <td>
                                2 متن یک
                            </td>
                            <td>
                                2 متن دو
                            </td>
                            <td>
                                2 متن سه
                            </td>
                        </tr>
                        <tr>
                            <td>
                                3 متن یک
                            </td>
                            <td>
                                3 متن دو
                            </td>
                            <td>
                                3 متن سه
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

در اینجا ذکر caption اختیاری است. وجود thead و tbody به تشخیص هدر و ردیف‌ها جهت اعمال شیوه‌نامه‌های متناظر کمک می‌کنند. همچنین کلاس‌های ذیل نیز به جدول اعمال شده‌اند:
table: سبب می‌شود تا تنظیمات ابتدایی bootstrap به جدول طراحی شده، اعمال شوند.
 table-striped: رنگ زمینه سطرها را یک در میان تغییر می‌دهد.
 table-hover: سبب می‌شود تا با عبور اشاره‌گر ماوس از روی سطرها، رنگ زمینه آن‌ها تغییر کنند.
 table-bordered: حاشیه‌ای را به جدول و ردیف‌ها اعمال می‌کنند. همچنین سبب نمایش گوشه‌های گرد نیز می‌شود.
 table-condensed: اندکی padding اعمال شده به سلول‌های جداول را کاهش می‌دهد و جدول را فشرده‌تر می‌کند.

در جدول فوق، کلاس نمونه error به یک tr نیز اعمال شده‌است تا اثر آن‌را بر روی یک ردیف بهتر بتوان ملاحظه کرد.



طراحی فرم‌ها و تاثیر bootstrap بر آن‌ها

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

1) فرم‌های عمودی
 

    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span12">
                <form action="/signup" method="post">
                <fieldset>
                    <legend>ثبت نام</legend>
                    <label>
                        ایمیل:</label>
                    <input name="email" type="text" />
                    <label>
                        نام:</label>
                    <input name="name" type="text" />
                </fieldset>
                </form>
            </div>
        </div>
    </div>

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

نکته: اگر می‌خواهید همان حالت پیش فرض مرورگر، یعنی قرار دادن تمام عناصر در پشت سر هم را فعال کنید، تنها کافی است کلاس form-inline را به form تعریف شده فوق اضافه نمائید.

2) نکاتی در مورد checkboxes و radio buttons
در حالت پیش فرض، با تعریف یک label و checkbox، برچسب متناظر با آن اندکی بالاتر از checkbox قرار گرفته شده در صفحه ظاهر می‌شود. برای رفع این مشکل تنها کافی است کلاس checkbox به label اعمال شود (و برای radio button از کلاس radio استفاده خواهد شد):
<label class="checkbox">
       <input type="checkbox" name="isMale" />
 مذکر</label>
این مثال را یکبار با کلاس checkbox و بار دیگر بدون آن آزمایش کنید تا تفاوت را بهتر بتوان درک کرد.

نکته: برای قرار دادن چندین checkbox یا radio button در یک سطر (با توجه به حالت چیدمان عمودی پیش فرض فرم‌ها)، ابتدا  آن‌ها را داخل یک div قرار دهید. سپس به تمام checkboxها یا radio buttonها کلاس inline را نیز اضافه نمائید. برای مثال:

 
 
                  <div>
                        <label class="radio inline">
                            <input type="radio" name="isMale" />
                            مذکر</label>
                        <label class="radio inline">
                            <input type="radio" name="isFemale" />
                            مؤنث</label>
                    </div>

 4) تعیین اندازه فیلدها

با استفاده از کلاس‌هایی مانند input-mini، input-small، input-medium، input-large، input-xlarge و input-xxlarge می‌توان اندازه فیلدها را کوچک‌تر از اندازه معمول یا بزرگتر کرد. یا حتی می‌توان از همان کلاس‌های span مانند span12 (اختصاص کل عرض به یک فیلد) و امثال آن برای تعیین اندازه یک فیلد استفاده کرد.
اگر علاقمند هستید که یک textarea کل عرض صفحه را به خود اختصاص دهد، از کلاس input-block-level استفاده کنید.

5) فرم‌های جستجو

 
 
       <form class="search-form">
                <fieldset>
                    <legend>جستجو</legend>
                    <input type="search" class="search-query" />
                    <button type="submit" class="btn" >بیاب</button>
                </fieldset>
       </form>

چند نکته در فرم‌های جستجوی طراحی شده با bootstrap نسبت به بقیه فرم‌ها حائز اهمیت است:
- کلاس فرم بهتر است search-form تعیین شود
- نوع input بهتر است search وارد شود
- کلاس input جستجو نیز search-query انتخاب گردد

همانطور که ملاحظه می‌کنید، در این حالت گوشه‌های جعبه متنی جستجو، نسبت به حالت‌های معمولی آن‌ها گرد شده است. کلاس دکمه نیز btn درنظر گرفته شده است تا حالت ویژه دکمه‌های bootstrap را پیدا کند.

6) فرم‌های افقی

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

 
                <form class="form-horizontal" action="/signup" method="post">
                <fieldset>
                    <legend>ثبت نام</legend>
                    <div class="control-group">
                        <label class="control-label">
                            ایمیل:</label>
                        <div class="controls">
                            <input name="email" type="text" />
                        </div>
                    </div>
                    <div class="control-group">
                        <label class="control-label">
                            نام:</label>
                        <div class="controls">
                            <input name="name" type="text" />
                        </div>
                    </div>
                    <div class="control-group">
                        <div class="controls">
                            <label class="radio inline">
                                <input type="radio" name="isMale" />
                                مذکر</label>
                            <label class="radio inline">
                                <input type="radio" name="isFemale" />
                                مؤنث</label>
                        </div>
                    </div>
                </fieldset>
                </form>

مطابق مثال فوق، خلاصه طراحی فرم‌های افقی با Bootstrap به این نحو است:
- کلاس form-horizontal را به فرم جاری اضافه کنید.
- هر سطر مورد نظر را در div ایی با کلاس control-group محصور نمائید.
- به برچسب‌ها، کلاس control-label را انتساب دهید.
- کنترل‌های مدنظر را در div ایی با کلاس controls محصور کنید.

هر چند این روش نیاز به اندکی HTML نویسی دارد، اما بسیاری به این نوع فرم‌ها بیشتر علاقمند هستند تا فرم‌های عمودی ابتدای بحث.

7) جلب توجه کاربران به فیلدها برای نمایش خطاهای اعتبارسنجی
 

<div class="control-group error">
   <label class="control-label">
                            ایمیل:</label>
    <div class="controls">
           <input name="email" type="text" />
           <span class="help-block">لطفا ایمیل را با فرمت صحیحی وارد نمائید</span>
     </div>
</div>

 
برای تزریق خطاهای اعتبارسنجی ویژه، در اینجا می‌توان از کلاس‌های help-block و یا help-inline به همراه کلاس‌هایی مانند error، info، warning و success استفاده کرد.

8) توسعه فیلدهای استاندارد
 

                <div class="input-prepend input-append">
                    <span dir="ltr" class="add-on">.00</span>
                    <input dir="ltr" type="text" />
                    <span class="add-on">ریال</span>
                </div>

 
توسط ترکیب کلاس‌های input-prepend، input-append و spanهایی با کلاس add-on، می‌توان عناصری بصری خاصی را به عنوان جزئی از فیلد مورد نظر اضافه کرد.

9) HTML Helpers مخصوص ASP.NET MVC برای کار با bootstrap

نکاتی را که در اینجا مطرح شدند، اگر علاقمند بودید که به شکلی strongly typed در ASP.NET MVC اعمال کنید، می‌توان به پروژه‌هایی مانند TwitterBootstrapMvc مراجعه کرد. تعداد این نوع پروژه‌ها هم روز به روز بیشتر می‌شوند:
https://twitterbootstrapmvc.codeplex.com/
https://mvc4bootstaphelper.codeplex.com/
https://github.com/erichexter/twitter.bootstrap.mvc
http://bootstraphelpers.codeplex.com/



تنظیمات خاص دکمه‌ها در حین استفاده از Twitter Bootstrap

در مثال ذیل، کلاس‌های مرتبط با تزئین دکمه‌ها را توسط bootstrap، ملاحظه می‌کنید:
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span12">
                <table
 class="table table-striped table-hover table-bordered table-condensed">
                    <thead>
                        <tr>
                            <th>
                                دکمه
                            </th>
                            <th>
                                لینک
                            </th>
                            <th>
                                کلاس بکار گرفته شده
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <button class="btn">
                                    default</button>
                            </td>
                            <td>
                                <a href="#" class="btn">default</a>
                            </td>
                            <td>
                                <code>btn</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-primary">
                                    primary</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-primary">primary</a>
                            </td>
                            <td>
                                <code>btn btn-primary</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-info">
                                    info</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-info">info</a>
                            </td>
                            <td>
                                <code>btn btn-info</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-success">
                                    success</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-success">success</a>
                            </td>
                            <td>
                                <code>btn btn-success</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-warning">
                                    warning</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-warning">warning</a>
                            </td>
                            <td>
                                <code>btn btn-warning</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-danger">
                                    danger</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-danger">danger</a>
                            </td>
                            <td>
                                <code>btn btn-danger</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-inverse">
                                    inverse</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-inverse">inverse</a>
                            </td>
                            <td>
                                <code>btn btn-inverse</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-link">
                                    link</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-link">link</a>
                            </td>
                            <td>
                                <code>btn btn-link</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-primary btn-large">
                                    large</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-primary btn-large">large</a>
                            </td>
                            <td>
                                <code>btn btn-primary btn-large</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-primary btn-small">
                                    small</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-primary btn-small">small</a>
                            </td>
                            <td>
                                <code>btn btn-primary btn-small</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-primary btn-mini">
                                    mini</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-primary btn-mini">mini</a>
                            </td>
                            <td>
                                <code>btn btn-primary btn-mini</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-primary btn-block">
                                    block</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-primary btn-block">block</a>
                            </td>
                            <td>
                                <code>btn btn-primary btn-block</code>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <button class="btn btn-primary disabled">
                                    disabled</button>
                            </td>
                            <td>
                                <a href="#" class="btn btn-primary disabled">disabled</a>
                            </td>
                            <td>
                                <code>btn btn-primary disabled</code>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

همانطور که ملاحظه کردید، الزامی ندارد که این کلاس‌ها را حتما به دکمه‌ها اعمال کرد. برای نمونه می‌توان از یک span یا لینک نیز برای تعریف دکمه‌ها بهره جست. برای یکپارچه سازی چنین دکمه‌هایی (که در اصل دکمه نیستند) با ASP.NET MVC می‌توان به مطلب تکمیلی «استفاده از دکمه‌های CSS توئیتر در ASP.NET MVC» مراجعه نمود.

یک نکته: اگر علاقمند هستید که تعدادی دکمه را به شکل یک toolbar نمایش دهید، آن‌ها را در یک div محصور کرده و کلاس btn-group را به آن div اعمال نمائید.


کار با تصاویر و آیکون‌ها در Twitter Bootstrap

کلاس‌هایی مانند img-rounded، img-circle، img-polaroid با اعمال به یک تصویر، سبب گردن شدن گوشه‌های آن، نمایش دایره‌ای و یا نمایش به همراه حاشیه یک تصویر خواهند شد.
Twitter Bootstrap به همراه صدها آیکون ارائه شده است. این آیکون‌ها توسط glyphicons.com ایجاد شده‌اند. روشی که برای استفاده از آن‌ها توصیه شده است، استفاده از تگ i می‌باشد. برای مثال:
<i class="icon-music"></i>icon-music
البته بدیهی است که محدودیتی در اینجا وجود نداشته و می‌توان این کلاس‌ها را به یک span نیز اعمال کرد.
برای مشاهده لیست کلاس‌های قابل استفاده، کلمه icon-glass را در فایل bootstrap.css جستجو نمائید؛ تا شروع مدخل مرتبط با آیکون‌ها را بتوانید مشاهده نمائید.
رنگ پیش فرض این آیکون‌ها مشکی است. اگر علاقمند بودید که آن‌ها را برای مثال با رنگ سفید نمایش دهید فقط کافی است کلاس icon-white را پس از کلاس آیکون مدنظر،ذکر کرد:
<i class="icon-music icon-white"></i>icon-music
از این نمونه قلم‌های سازگار با Twitter Bootstrap در آدرس fontawesome.github.com نیز قابل دریافت هستند.
امکان اعمال این آیکون‌ها به دکمه‌ها نیز وجود دارد. برای مثال:
    <button class="btn">
        <i class="icon-music"></i>دکمه</button>
کاربرد دیگر این آیکون‌ها در بحث توسعه فیلدهای استاندارد است که پیشتر بحث شد. برای مثال نمایش آیکون نامه در کنار فیلد دریافت ایمیل:
 

 
    <div class="input-prepend">
        <span class="add-on"><i class="icon-envelope"></i></span>
        <input type="email" dir="ltr" />
    </div>


نظرات اشتراک‌ها
پروژه SmartUnderline
- کاری به ASP.NET MVC سمت سرور ندارد.
- مثال آن در اینجا ارائه شده. روی آن کلیک راست کنید و سورس آن‌را مشاهده کنید. اسکریپت آن باید الحاق شود و متد init دارد در ابتدای کار. همچنین فایل‌های CSS خاصی هم نیاز دارد. برای مشاهده‌ی یکجای فایل‌های CSS آن از افزونه‌ی web developer کمک بگیرید. این افزونه قابلیت نمایش یکجای فایل‌های اسکریپت آن صفحه را هم دارد. کلا برای مهندسی معکوس این نوع صفحات گنگ بسیار مفید است.
نظرات مطالب
معرفی JSON Web Token
خیلی ممنون از مطلب مفیدتون.
روش token base authentication روش بسیار خوبی برای جایگزین شدن با روش‌های پیشین است، به خصوص در برنامه‌های تک صفحه ای. سوالی که برای بنده مطرح شد. بنده در پروژه ای token و اطلاعات تکمیلی کاربر را در Local Storage ذخیره میکنم. حال اینکه وقتی token به کاربر داده شد، اگر اطلاعات را کپی کنم و در مرورگر دیگری این اطلاعات را وارد کنم من را تایید صلاحیت میکنه. در صورتی که این نکته میتونه خیلی خطر ساز باشه، چون token که به کاربر اعطا میشه کلید refresh token را هم به همراه خودش داره و در نهایت کاربر تا هر زمانی که بخواد میتونه لاگین شده باقی بمونه. ممنون میشم اگر راه حلی پیشنهاد بدید به بنده.
یک سوالی در مورد عملکرد برنامه تلگرام هم دیده بودم، در تلگرام هم فکر میکنم از token استفاده میشه. اما خیلی جالبه که شما میتونید sessionهای فعالتون را ببینید که از چند دستگاه لاگین شده اید. آیا اگر اطلاعات token را در دیتابیس نگه دارم میتونم مثل تلگرام این وضعیت را هم کنترل کنم؟ مثلا آیا میتونم از سمت سرور یک token که تایید صلاحیت هست را expire کنم؟
مطالب دوره‌ها
پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC
مدتی است در اکثر سایت‌ها و طراحی‌های جدید، به جای استفاده از روش متداول نمایش انتخاب صفحه 1، 2 ... 100، برای صفحه بندی اطلاعات، از روش اسکرول نامحدود یا infinite scroll استفاده می‌کنند. نمونه‌ای از آن‌را هم در سایت جاری با دکمه «بیشتر» در ذیل اکثر صفحات و مطالب سایت مشاهده می‌کنید.


در ادامه قصد داریم نحوه پیاده سازی آن‌را در ASP.NET MVC به کمک امکانات jQuery بررسی کنیم.


مدل برنامه

namespace jQueryMvcSample02.Models
{
    public class BlogPost
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }
}
در این برنامه و مثال، قصد داریم لیستی از مطالب را توسط اسکرول نامحدود، نمایش دهیم. هر آیتم نمایش داده شده، ساختاری همانند کلاس BlogPost دارد.


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

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

namespace jQueryMvcSample02.DataSource
{
    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 });
            }
            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();
        }
    }
}
برای اینکه برنامه نهایی را به سادگی بتوانید اجرا کنید، به عمد از بانک اطلاعاتی خاصی استفاده نشده و صرفا یک منبع داده فرضی تشکیل شده در حافظه، در اینجا مورد استفاده قرار گرفته است. بدیهی است قسمت cachedItems را به سادگی می‌توانید با یک ORM جایگزین کنید.
تنها نکته مهم آن، نحوه تعریف متد GetLatestBlogPosts می‌باشد که برای صفحه بندی اطلاعات بهینه سازی شده است. در اینجا توسط متدهای Skip و Take، تنها بازه‌ای از اطلاعات که قرار است نمایش داده شوند، دریافت می‌گردد. خوشبختانه این متدها معادل‌های مناسبی را در اکثر بانک‌های اطلاعاتی داشته و استفاده از آن‌ها بر روی یک بانک اطلاعاتی واقعی نیز بدون مشکل کار می‌کند و تنها بازه محدودی از اطلاعات را واکشی خواهد کرد که از لحاظ مصرف حافظه و سرعت کار بسیار مقرون به صرفه و سریع است.


کنترلر برنامه

using System.Linq;
using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample02.DataSource;
using jQueryMvcSample02.Security;

namespace jQueryMvcSample02.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            //آغاز کار با صفحه صفر است
            var list = BlogPostDataSource.GetLatestBlogPosts(pageNumber: 0);
            return View(list); //نمایش ابتدایی صفحه
        }

        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public virtual ActionResult PagedIndex(int? page)
        {
            var pageNumber = page ?? 0;
            var list = BlogPostDataSource.GetLatestBlogPosts(pageNumber);
            if (list == null || !list.Any())
                return Content("no-more-info"); //این شرط ما است برای نمایش عدم یافتن رکوردها

            return PartialView("_ItemsList", list);
        }

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

            //todo: show the content here
            return Content("Post " + id.Value);
        }
    }
}
کنترلر برنامه را در اینجا ملاحظه می‌کنید. برای کار با اسکرول نامحدود، به ازای هر صفحه، نیاز به دو متد است:
الف) یک متد که بر اساس HttpGet کار می‌کند. این متد در اولین بار نمایش صفحه فراخوانی می‌گردد و اطلاعات صفحه آغازین را نمایش می‌دهد.
ب) متد دومی که بر اساس HttpPost کار کرده و محدود است به درخواستی‌های AjaxOnly همانند متد PagedIndex.
از این متد دوم برای پردازش کلیک‌های کاربر بر روی دکمه «بیشتر» استفاده می‌گردد. بنابراین تنها کاری که افزونه جی‌کوئری تدارک دیده شده ما باید انجام دهد، ارسال شماره صفحه است. سپس با استفاده از این شماره، بازه مشخصی از اطلاعات دریافت و نهایتا یک PartialView رندر شده برای افزوده شدن به صفحه بازگشت داده می‌شود.


دو View برنامه

همانطور که برای بازگشت اطلاعات نیاز به دو اکشن متد است، برای رندر اطلاعات نیز به دو View نیاز داریم:
الف) یک PartialView که صرفا لیستی از اطلاعات را مطابق سلیقه ما رندر می‌کند. از این PartialView در متد PagedIndex استفاده خواهد شد:
@model IList<jQueryMvcSample02.Models.BlogPost>
<ul>
    @foreach (var item in Model)
    {
        <li>
            <h5>
                @Html.ActionLink(linkText: item.Title,
                                 actionName: "Post",
                                 controllerName: "Home",
                                 routeValues: new  { id = item.Id },
                                 htmlAttributes: null)
            </h5>
            @item.Body
        </li>
    }
</ul>
ب) یک View کامل که در بار اول نمایش صفحه، مورد استفاده قرار می‌گیرد:
@model IList<jQueryMvcSample02.Models.BlogPost>
@{
    ViewBag.Title = "Index";
    var loadInfoUrl = Url.Action(actionName: "PagedIndex", controllerName: "Home");
}
<h2>
    اسکرول نامحدود</h2>
@{ Html.RenderPartial("_ItemsList", Model); }
<div id="MoreInfoDiv">
</div>
<div align="center" style="margin-bottom: 9px;">
    <span id="moreInfoButton" style="width: 90%;" class="btn btn-info">بیشتر</span>
</div>
<div id="ProgressDiv" align="center" style="display: none">
    <br />
    <img src="@Url.Content("~/Content/images/loadingAnimation.gif")" alt="loading..."  />
</div>
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#moreInfoButton").InfiniteScroll({
                moreInfoDiv: '#MoreInfoDiv',
                progressDiv: '#ProgressDiv',
                loadInfoUrl: '@loadInfoUrl',
                loginUrl: '/login',
                errorHandler: function () {
                    alert('خطایی رخ داده است');
                },
                completeHandler: function () {
                    // اگر قرار است روی اطلاعات نمایش داده شده پردازش ثانوی صورت گیرد
                },
                noMoreInfoHandler: function () {
                    alert('اطلاعات بیشتری یافت نشد');
                }
            });
        });
    </script>
}
چند نکته در اینجا حائز اهمیت است:
1) مسیر دقیق اکشن متد PagedIndex توسط متد  Url.Action تهیه شده است.
2) در ابتدای نمایش صفحه، متد Html.RenderPartial کار نمایش اولیه اطلاعات را انجام خواهد داد.
3) از div خالی MoreInfoDiv، به عنوان محل افزوده شدن اطلاعات Ajax ایی دریافتی استفاده می‌کنیم.
4) دکمه بیشتر در اینجا تنها یک span ساده است که توسط css به شکل یک دکمه نمایش داده خواهد شد (فایل‌های آن در پروژه پیوست موجود است).
5) ProgressDiv در ابتدای نمایش صفحه مخفی است. زمانیکه کاربر بر روی دکمه بیشتر کلیک می‌کند، توسط افزونه جی‌کوئری ما نمایان شده و در پایان کار مجددا مخفی می‌گردد.
6) section JavaScript کار استفاده از افزونه InfiniteScroll را انجام می‌دهد.


و کدهای افزونه اسکرول نامحدود

// <![CDATA[
(function ($) {
    $.fn.InfiniteScroll = function (options) {
        var defaults = {
            moreInfoDiv: '#MoreInfoDiv',
            progressDiv: '#Progress',
            loadInfoUrl: '/',
            loginUrl: '/login',
            errorHandler: null,
            completeHandler: null,
            noMoreInfoHandler: null
        };
        var options = $.extend(defaults, options);

        var showProgress = function () {
            $(options.progressDiv).css("display", "block");
        }

        var hideProgress = function () {
            $(options.progressDiv).css("display", "none");
        }       

        return this.each(function () {
            var moreInfoButton = $(this);
            var page = 1;
            $(moreInfoButton).click(function (event) {
                showProgress();
                $.ajax({
                    type: "POST",
                    url: options.loadInfoUrl,
                    data: JSON.stringify({ page: page }),
                    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 == "no-more-info") {
                                if (options.noMoreInfoHandler)
                                    options.noMoreInfoHandler(this);
                            }
                            else {
                                var $boxes = $(data);
                                $(options.moreInfoDiv).append($boxes);
                            }
                            page++;
                        }
                        hideProgress();
                        if (options.completeHandler)
                            options.completeHandler(this);
                    }
                });
            });
        });
    };
})(jQuery);
// ]]>
ساختار افزونه اسکرول نامحدود به این شرح است:
هربار که کاربر بر روی دکمه بیشتر کلیک می‌کند، progress div ظاهر می‌گردد. سپس توسط امکانات jQuery Ajax، شماره صفحه (بازه انتخابی) به اکشن متد صفحه بندی اطلاعات ارسال می‌گردد. در نهایت اطلاعات را از کنترلر دریافت و به moreInfoDiv اضافه می‌کند. در آخر هم شماره صفحه را یکی افزایش داده و سپس progress div را مخفی می‌کند.

دریافت مثال و پروژه کامل این قسمت
jQueryMvcSample02.zip
 
نظرات مطالب
استفاده از قابلیت پارتیشن بندی در آرشیو جداول بانک‌های اطلاعاتی SQL Server
با سلام.
من چندتا نکته بگم که شاید مساله‌ی شما بنیادی‌تر رفع بشه:
1- شما تاریخ رو به صورت شمسی در دیتابیس ذخیره میکنید. این خودش مسائل زیر را ایجاد میکنه:
الف) شما هیچ کنترلی برای اعتبارسنجی در سطح دیتابیس برای تاریخ تون ندارید (یعنی 31-12-1398 از نظر دیتابیس مشکلی نداره!) از طرف دیگه مقایسه تاریخی شما با هزینه همراه هستش (یعنی شما وقتی میخواید کوئیری بزنید که مثلا داده هایی که در بین دوتا بازه هستن و براتون لود کنه)
ب) شما قابلیت اجرای کوئیری‌ها مثلا یک ماه اخیر و ... رو به سختی در سطح دیتابیس دارید. (البته راه حل داره که الان از این موضوع خارجه)
2- حجم دیتابیس! شما برای این فیلد به 10 کاراکتر نیاز دارید که میشه 10 بایت! در صورتی که به صورت خیلی ساده! فقط نوع ستون رو به int تغییر بدید و این مقدار رو به صورت 13951230 نگه داری کنید، حداقل 60 درصد کاهش فضا در این فیلد دارید! مزایای بیشتری هم داره از جمله مقایسه دوتا عدد خیلی کم هزینه‌تر از مقایسه دوتا رشته هستش و ....
مستقل از مسائل طراحی موجود در طراحی شما (که الان خارج از موضوع هستش) به نظرم شما باید کوئیری که میزنید رو هم بذارید، چون کلید پارتیشن باید توی کوئیری دخیل شده باشه که دیتابیس بتونه براساس اون، پارتیشن یا پارتیشن‌های مورد نظر را فیلتر کنه و مجبور نباشه روی کل دیتابیس بچرخه!
مطالب
Globalization در ASP.NET MVC - قسمت ششم
در قسمت قبل ساختار اصلی و پیاده‌سازی ابتدایی یک پرووایدر سفارشی دیتابیسی شرح داده شد. در این قسمت ادامه بحث و مطالب پیشرفته‌تر آورده شده است.

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


نام این جدول را با درنظر گرفتن شرایط موجود می‌توان Resources گذاشت.

ستون Name برای ذخیره نام منبع درنظر گرفته شده است. این نام برابر نام منابع درخواستی در سیستم مدیریت منابع ASP.NET است که درواقع برابر همان نام فایل منبع اما بدون پسوند resx. است.

ستون Key برای نگهداری کلید ورودی منبع استفاده می‌شود که دقیقا برابر همان مقداری است که درون فایلهای resx. ذخیره می‌شود. 

ستون Culture برای ذخیره کالچر ورودی منبع به کار می‌رود. این مقدار می‌تواند برای کالچر پیش‌فرض برنامه برابر رشته خالی باشد. 

ستون Value نیز برای نگهداری مقدار ورودی منبع استفاده می‌شود. 

برای ستون Id می‌توان از GUID نیز استفاده کرد. در اینجا برای راحتی کار از نوع داده bigint و خاصیت Identity برای تولید خودکار آن در Sql Server استفاده شده است.

نکته: برای امنیت بیشتر می‌توان یک Unique Constraint بر روی سه فیلد Name و Key و Culture اعمال کرد.

برای نمونه به تصویر زیر که ذخیره تعدای ورودی منبع را درون جدول Resources نمایش می‌دهد دقت کنید:

 

اصلاح کلاس DbResourceProviderFactory

برای ذخیره منابع محلی، جهت اطمینان از یکسان بودن نام منبع، متد مربوطه در کلاس DbResourceProviderFactory باید به‌صورت زیر تغییر کند:

public override IResourceProvider CreateLocalResourceProvider(string virtualPath)
{
  if (!string.IsNullOrEmpty(virtualPath))
  {
    virtualPath = virtualPath.Remove(0, virtualPath.IndexOf('/') + 1); // removes everything from start to the first '/'
  }
  return new LocalDbResourceProvider(virtualPath);
}
با این تغییر مسیرهای درخواستی چون "Default.aspx/~" و یا "Default.aspx/" هر دو به صورت "Default.aspx" در می‌آیند تا با نام ذخیره شده در دیتابیس یکسان شوند.
 

ارتباط با دیتابیس

خوشبختانه برای تبادل اطلاعات با جدول بالا امروزه راه‌های زیادی وجود دارد. برای پیاده‌سازی آن مثلا می‌توان از یک اینترفیس استفاده کرد. سپس با استفاده از سازوکارهای موجود مثلا به‌کارگیری IoC، نمونه مناسبی از پیاده‌سازی اینترفیس مذبور را در اختیار برنامه قرار داد.
اما برای جلوگیری از پیچیدگی بیش از حد و دور شدن از مبحث اصلی، برای پیاده‌سازی فعلی از EF Code First به صورت مستقیم در پروژه استفاده شده است که سری آموزشی کاملی از آن در همین سایت وجود دارد.

پس از پیاده‌سازی کلاس‌های مرتبط برای استفاده از EF Code First، از کلاس ResourceData که در بخش اول نیز نشان داده شده بود، برای کپسوله کردن ارتباط با داده‌ها استفاده می‌شود که نمونه‌ای ابتدایی از آن در زیر آورده شده است:

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

namespace DbResourceProvider.Data
{
  public class ResourceData
  {
    private readonly string _resourceName;
    public ResourceData(string resourceName)
    {
      _resourceName = resourceName;
    }
    public Resource GetResource(string resourceKey, string culture)
    {
      using (var data = new TestContext())
      {
        return data.Resources.SingleOrDefault(r => r.Name == _resourceName && r.Key == resourceKey && r.Culture == culture);
      }
    }
    public List<Resource> GetResources(string culture)
    {
      using (var data = new TestContext())
      {
        return data.Resources.Where(r => r.Name == _resourceName && r.Culture == culture).ToList();
      }
    }
  }
}
کلاس فوق نسبت به نمونه‌ای که در قسمت قبل نشان داده شد کمی فرق دارد. بدین صورت که برای راحتی بیشتر نام منبع درخواستی به جای پارامتر متدها، در اینجا به عنوان پارامتر کانستراکتور وارد می‌شود.

نکته: درصورتی‌که این کلاس‌ها در پروژه‌ای جداگانه قرار دارند، باید ConnectionString مربوطه در فایل کانفیگ برنامه مقصد نیز تنظیم شود.

کش کردن ورودی‌ها
برای کش کردن ورودی‌ها این نکته را که قبلا هم به آن اشاره شده بود باید درنظر داشت:
پس از اولین درخواست برای هر منبع، نمونه تولیدشده از پرووایدر مربوطه در حافظه سرور کش خواهد شد.
یعنی متدهای کلاس DbResourceProviderFactory به‌ازای هر منبع تنها یکبار فراخوانی می‌شود. نمونه‌های کش‌شده از پروایدرهای کلی و محلی به همراه تمام محتویاتشان (مثلا نمونه تولیدی از کلاس DbResourceManager) تا زمان Unload شدن سایت در حافظه سرور باقی می‌مانند. بنابراین عملیات کشینگ ورودی‌ها را می‌توان درون خود کلاس DbResourceManager به ازای هر منبع انجام داد.
برای کش کردن ورودی‌های هر منبع می‌توان چند روش را درپیش گرفت. روش اول این است که به ازای هر کلید درخواستی تنها ورودی مربوطه از دیتابیس فراخوانی شده و در برنامه کش شود. این روش برای حالاتی که تعداد ورودی‌ها یا تعداد درخواست‌های کلیدهای هر منبع کم باشد مناسب خواهد بود.
یکی از پیاده‌سازی این روش این است که ورودی‌ها به ازای هر کالچر ذخیره شوند. پیاده‌سازی اولیه این نوع فرایند کشینگ در کلاس DbResourceManager به صورت زیر است:
using System.Collections.Generic;
using System.Globalization;
using DbResourceProvider.Data;
namespace DbResourceProvider
{
  public class DbResourceManager
  {
    private readonly string _resourceName;
    private readonly Dictionary<string, Dictionary<string, object>> _resourceCacheByCulture;
    public DbResourceManager(string resourceName)
    {
      _resourceName = resourceName;
      _resourceCacheByCulture = new Dictionary<string, Dictionary<string, object>>();
    }
    public object GetObject(string resourceKey, CultureInfo culture)
    {
      return GetCachedObject(resourceKey, culture.Name);
    }
    private object GetCachedObject(string resourceKey, string cultureName)
    {
      if (!_resourceCacheByCulture.ContainsKey(cultureName))
        _resourceCacheByCulture.Add(cultureName, new Dictionary<string, object>());
      var cachedResource = _resourceCacheByCulture[cultureName];
      lock (this)
      {
        if (!cachedResource.ContainsKey(resourceKey))
        {
          var data = new ResourceData(_resourceName);
          var dbResource = data.GetResource(resourceKey, cultureName);
          if (dbResource == null) return null;
          var cachedResources = _resourceCacheByCulture[cultureName];
          cachedResources.Add(dbResource.Key, dbResource.Value);
        }
      }
      return cachedResource[resourceKey];
    }
  }
}
همانطور که قبلا توضیح داده شد کشِ پرووایدرهای منابع به ازای هر منبع درخواستی (و به تبع آن نمونه‌های موجود در آن مثل DbResourceManager) برعهده خود ASP.NET است. بنابراین برای کش کردن ورودی‌های درخواستی هر منبع در کلاس DbResourceManager تنها کافی است آن‌ها را درون یک متغیر محلی در سطح کلاس (فیلد) ذخیره کرد. کاری که در کد بالا در متغیر resourceCacheByCulture_ انجام شده است. در این متغیر که از نوع دیکشنری تعریف شده است کلیدهای هر عضو آن برابر نام کالچر مربوطه است. مقادیر هر عضو این دیکشنری نیز خود یک دیکشنری است که ورودی‌های منابع مربوط به کالچر مربوطه در آن ذخیره می‌شوند.
عملیات در متد GetCachedObject انجام می‌شود. همان‌طور که می‌بینید ابتدا وجود ورودی موردنظر در متغیر کشینگ بررسی می‌شود و درصورت عدم وجود، مقدار آن مستقیما از دیتابیس درخواست می‌شود. سپس این مقدار درخواستی ابتدا درون متغیر کشینگ ذخیره شده (به همراه بلاک lock) و درنهایت برگشت داده می‌شود.

نکته: کل فرایند بررسی وجود کلید در متغیر کشینگ (شرط دوم در متد GetCachedObject) درون بلاک lock قرار داده شده است تا در درخواست‌های همزمان احتمال افزودن چندباره یک کلید ازبین برود.

پیاده‌سازی دیگر این فرایند کشینگ، ذخیره ورودی‌ها براساس نام کلید به جای نام کالچر است. یعنی کلید دیکشنری اصلی نام کلید و کلید دیکشنری داخلی نام کالچر است که این روش زیاد جالب نیست.
روش دوم که بیشتر برای برنامه‌های بزرگ با ورودی‌ها و درخواست‌های زیاد به‌کار می‌رود این است که درهر بار درخواست به دیتابیس به جای دریافت تنها همان ورودی درخواستی، تمام ورودی‌های منبع و کالچر درخواستی استخراج شده و کش می‌شود تا تعداد درخواست‌های به سمت دیتابیس کاهش یابد. برای پیاده‌سازی این روش کافی است تغییرات زیر در متد GetCachedObject اعمال شود:
private object GetCachedObject(string resourceKey, string cultureName)
{
  lock (this)
  {
    if (!_resourceCacheByCulture.ContainsKey(cultureName))
    {
      _resourceCacheByCulture.Add(cultureName, new Dictionary<string, object>());
      var cachedResources = _resourceCacheByCulture[cultureName];
      var data = new ResourceData(_resourceName);
      var dbResources = data.GetResources(cultureName);
      foreach (var dbResource in dbResources)
      {
        cachedResources.Add(dbResource.Key, dbResource.Value);
      }
    }
  }
  var cachedResource = _resourceCacheByCulture[cultureName];
  return !cachedResource.ContainsKey(resourceKey) ? null : cachedResource[resourceKey];
}
دراینجا هم می‌توان به جای استفاده از نام کالچر برای کلید دیکشنری اصلی از نام کلید ورودی منبع استفاده کرد که چندان توصیه نمی‌شود.

نکته: انتخاب یکی از دو روش فوق برای فرایند کشینگ کاملا به شرایط موجود و سلیقه برنامه نویس بستگی دارد.

فرایند Fallback
درباره فرایند fallback به اندازه کافی در قسمت‌های قبلی توضیح داده شده است. برای پیاده‌سازی این فرایند ابتدا باید به نوعی به سلسله مراتب کالچرهای موجود از کالچر جاری تا کالچر اصلی و پیش فرض سیستم دسترسی پیدا کرد. برای اینکار ابتدا باید با استفاده از روشی کالچر والد یک کالچر را بدست آورد. کالچر والد کالچری است که عمومیت بیشتری نسبت به کالچر موردنظر دارد. مثلا کالچر fa، کالچر والد fa-IR است. همچنین کالچر Invariant به عنوان والد تمام کالچرها شناخته می‌شود.
خوشبختانه در کلاس CultureInfo (که در قسمت‌های قبلی شرح داده شده است) یک پراپرتی با عنوان Parent وجود دارد که کالچر والد را برمی‌گرداند.
برای رسیدن به سلسله مراتب مذبور در کلاس ResourceManager دات نت، از کلاسی با عنوان ResourceFallbackManager استفاده می‌شود. هرچند این کلاس با سطح دسترسی internal تعریف شده است اما نام‌گذاری نامناسبی دارد زیرا کاری که می‌کند به عنوان Manager هیچ ربطی ندارد. این کلاس با استفاده از یک کالچر ورودی، یک enumerator از سلسله مراتب کالچرها که در بالا صحبت شد تهیه می‌کند.
با استفاده پیاده‌سازی موجود در کلاس ResourceFallbackManager کلاسی با عنوان CultureFallbackProvider تهیه کردم که به صورت زیر است:
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
namespace DbResourceProvider
{
  public class CultureFallbackProvider : IEnumerable<CultureInfo>
  {
    private readonly CultureInfo _startingCulture;
    private readonly CultureInfo _neutralCulture;
    private readonly bool _tryParentCulture;
    public CultureFallbackProvider(CultureInfo startingCulture = null, 
                                   CultureInfo neutralCulture = null, 
                                   bool tryParentCulture = true)
    {
      _startingCulture = startingCulture ?? CultureInfo.CurrentUICulture;
      _neutralCulture = neutralCulture;
      _tryParentCulture = tryParentCulture;
    }
    #region Implementation of IEnumerable<CultureInfo>
    public IEnumerator<CultureInfo> GetEnumerator()
    {
      var reachedNeutralCulture = false;
      var currentCulture = _startingCulture;
      do
      {
        if (_neutralCulture != null && currentCulture.Name == _neutralCulture.Name)
        {
          yield return CultureInfo.InvariantCulture;
          reachedNeutralCulture = true;
          break;
        }
        yield return currentCulture;
        currentCulture = currentCulture.Parent;
      } while (_tryParentCulture && !HasInvariantCultureName(currentCulture));
      if (!_tryParentCulture || HasInvariantCultureName(_startingCulture) || reachedNeutralCulture)
        yield break;
      yield return CultureInfo.InvariantCulture;
    }
    #endregion
    #region Implementation of IEnumerable
    IEnumerator IEnumerable.GetEnumerator()
    {
      return GetEnumerator();
    }
    #endregion
    private bool HasInvariantCultureName(CultureInfo culture)
    {
      return culture.Name == CultureInfo.InvariantCulture.Name;
    }
  }
}
این کلاس که اینترفیس <IEnumerable<CultureInfo را پیاده‎سازی کرده است، سه پارامتر کانستراکتور دارد.
اولین پارامتر، کالچر جاری یا آغازین را مشخص می‌کند. این کالچری است که تولید enumerator مربوطه از آن آغاز می‌شود. درصورتی‌که این پارامتر نال باشد مقدار کالچر UI در ثرد جاری برای آن درنظر گرفته می‌شود. مقدار پیش‌فرضی که برای این پارامتر درنظر گرفته شده است، null است.
پارامتر بعدی کالچر خنثی موردنظر کاربر است. این کالچری است که درصورت رسیدن enumerator به آن کار پایان خواهد یافت. درواقع کالچر پایانی enumerator است. این پارامتر می‌تواند نال باشد. مقدار پیش‌فرضی که برای این پارامتر درنظر گرفته شده است، null است.
پارمتر آخر هم تعیین می‌کند که آیا enumerator از کالچرهای والد استفاده بکند یا خیر. مقدار پیش‌فرضی که برای این پارامتر درنظر گرفته شده است، true است. 
کار اصلی کلاس فوق در متد GetEnumerator انجام می‌شود. در این کلاس یک حلقه do-while وجود دارد که enumerator را با استفاده از کلمه کلیدی yield تولید می‌کند. در این متد ابتدا درصورت نال نبودن کالچر خنثی ورودی، بررسی می‌شود که آیا نام کالچر جاری حلقه (که در متغیر محلی currentCulture ذخیره شده است) برابر نام کالچر خنثی است یا خیر. درصورت برقراری شرط، کار این حلقه با برگشت CultureInfo.InvariantCulture پایان می‌‌یابد. InvariantCulture کالچر بدون زبان و فرهنگ و موقعیت مکانی است که درواقع به عنوان کالچر والد تمام کالچرها درنظر گرفته می‌شود. پراپرتی Name این کالچر برابر string.Empty است.
کار حلقه با برگشت مقدار کالچر جاری enumerator ادامه می‌یابد. سپس کالچر جاری با کالچر والدش مقداردهی می‌شود. شرط قسمت while حلقه تعیین می‌کند که درصورتی‌که کلاس برای استفاده از کالچرهای والد تنظیم شده باشد، تا زمانی که نام کالچر جاری برابر نام کالچر Invariant نباشد، تولید اعضای enumerator ادامه یابد.
درانتها نیز درصورتی‌که با شرایط موجود، قبلا کالچر Invariant برگشت داده نشده باشد این کالچر نیز yield می‌شود. درواقع درصورتی‌که استفاده از کالچرهای والد اجازه داده نشده باشد یا کالچر آغازین برابر کالچر Invariant باشد و یا قبلا به دلیل رسیدن به کالچر خنثی ورودی، مقدار کالچر Invariant برگشت داده شده باشد، enumerator قطع شده و عملیات پایان می‌یابد. در غیر اینصورت کالچر Invariant به عنوان کالچر پایانی برگشت داده می‌شود.
 
استفاده از CultureFallbackProvider
با استفاده از کلاس CultureFallbackProvider می‌توان عملیات جستجوی ورودی‌های درخواستی را با ترتیبی مناسب بین تمام کالچرهای موجود به انجام رسانید.
برای استفاده از این کلاس باید تغییراتی در متد GetObject کلاس DbResourceManager به صورت زیر اعمال کرد:
public object GetObject(string resourceKey, CultureInfo culture)
{
  foreach (var currentCulture in new CultureFallbackProvider(culture))
  {
    var value = GetCachedObject(resourceKey, currentCulture.Name);
    if (value != null) return value;
  }
  throw new KeyNotFoundException("The specified 'resourceKey' not found.");
}
با استفاده از یک حلقه foreach درون enumerator کلاس CultureFallbackProvider، کالچرهای موردنیاز برای fallback یافته می‌شوند. در اینجا از مقادیر پیش‌فرض دو پارامتر دیگر کانستراکتور کلاس CultureFallbackProvider استفاده شده است.
سپس به ازای هر کالچر یافته شده مقدار ورودی درخواستی بدست آمده و درصورتی‌که نال نباشد (یعنی ورودی موردنظر برای کالچر جاری یافته شود) آن مقدار برگشت داده می‌شود و درصورتی‌که نال باشد عملیات برای کالچر بعدی ادامه می‌یابد.
درصورتی‌که ورودی درخواستی یافته نشود (خروج از حلقه بدون برگشت مقداری برای ورودی منبع درخواستی) استثنای KeyNotFoundException صادر می‌شود تا کاربر را از اشتباه رخداده مطلع سازد.

آزمایش پرووایدر سفارشی

ابتدا تنظیمات موردنیاز فایل کانفیگ را که در قسمت قبل نشان داده شد، در برنامه خود اعمال کنید.

داده‌های نمونه نشان داده شده در ابتدای این مطلب را درنظر بگیرید. حال اگر در یک برنامه وب اپلیکیشن، صفحه Default.aspx در ریشه سایت حاوی دو کنترل زیر باشد:

<asp:Label ID="Label1" runat="server" meta:resourcekey="Label1" />
<asp:Label ID="Label2" runat="server" meta:resourcekey="Label2" />
خروجی برای کالچر "en-US" (معمولا پیش‌فرض، اگر تنظیمات سیستم عامل تغییر نکرده باشد) چیزی شبیه تصویر زیر خواهد بود:

سپس تغییر زیر را در فایل web.config اعمال کنید تا کالچر UI سایت به fa تغییر یابد (به بخش "uiCulture="fa دقت کنید):

<globalization uiCulture="fa" resourceProviderFactoryType = "DbResourceProvider.DbResourceProviderFactory, DbResourceProvider" />
بنابراین صفحه Default.aspx با همان داده‌های نشان داده شده در بالا به صورت زیر تغییر خواهد کرد:

می‌بینید که با توجه به عدم وجود مقداری برای Label2.Text برای کالچر fa، عملیات fallback اتفاق افتاده است.

بحث و نتیجه‎‌گیری

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

اما نکته‌ای را که باید به آن توجه کنید این است که در پیاده‌سازی‌های نشان داده شده با توجه به نحوه کش‌شدن مقادیر ورودی‌ها، اگر این مقادیر در دیتابیس تغییر کنند، تا زمانیکه سایت ریست نشود این تغییرات در برنامه اعمال نخواهد شد. زیرا همانطور که اشاره شد، مدیریت نمونه‌های تولیدشده از پرووایدرهای منابع برای هر منبع درخواستی درنهایت برعهده ASP.NET است. بنابراین باید مکانیزمی پیاده شود تا کلاس DbResourceManager از به‌روزرسانی ورودی‌های کش‌شده اطلاع یابد تا آنها را ریفرش کند.

در ادامه درباره روش‌های مختلف نحوه پیاده‌سازی قابلیت به‌روزرسانی ورودی‌های منابع در زمان اجرا با استفاده از پرووایدرهای منابع سفارشی بحث خواهد شد. همچنین راه‌حل‌های مختلف استفاده از این پرووایدرهای سفارشی در جاهای مختلف پروژه‌های MVC شرح داده می‌شود.

البته مباحث پیشرفته‌تری چون تزریق وابستگی برای پیاده‌سازی لایه ارتباط با دیتابیس در بیرون و یا تولید یک Factory برای تزریق کامل پرووایدر منابع از بیرون نیز جای بحث و بررسی دارد.

منابع

http://weblogs.asp.net/thangchung/archive/2010/06/25/extending-resource-provider-for-soring-resources-in-the-database.aspx

http://msdn.microsoft.com/en-us/library/aa905797.aspx

http://msdn.microsoft.com/en-us/library/system.web.compilation.resourceproviderfactory.aspx

http://www.dotnetframework.org/default.aspx/.../ResourceFallbackManager@cs

http://www.codeproject.com/Articles/14190/ASP-NET-2-0-Custom-SQL-Server-ResourceProvider

http://www.west-wind.com/presentations/wwdbresourceprovider

اشتراک‌ها
فراخوانی بیشتر از یک بار "window.onload"
با توجه به اینکه بهترین مکان برای load کردن کتابخانه‌های javascript  در انتهای صفحه می‌باشد (از جمله jquery.js )، چنانچه از کدهای jquery در میان صفحات استفاده نماییم، مطمئنا با خطا مواجه خواهیم شد.
راه کار اولیه که به ذهنم رسید قرار دادن کد‌ها در یک تابع جدید مانند کد زیر است :
function myfunc(){
    $('#test')....;
}
و سپس قرار دادن آن تابع در window.onload به صورت زیر
window.onload = myfunc;
اما چنانچه در صفحه بیشتر از یک بار این مقدار دهی صورت گیرد با خطا مواجه می‌شویم

و خلاصه اینکه در لینک برای این مسئله هم راه کار وجود دارد و آن :

window.addEventListener('load', myfunc1, false);
window.addEventListener('load', myfunc2, false);
...




فراخوانی بیشتر از یک بار "window.onload"