اشتراک‌ها
چگونه باگ‌ها را گزارش دهیم؟

«... یکی از مهمترین جنبه‌های یک گزارش باگ یا اشکال این است که توسعه‌دهنده باید بتواند از روی آن گزارش، موقعیت و مشکل مورد نظر را بازسازی نماید ...»

چگونه باگ‌ها را گزارش دهیم؟
نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند

این پروژه یک فایل اسکریپت بیشتر نیست. بنابراین برای کار کردن نیاز به الحاق آن به صفحه هست (مثل تمام پروژه‌های جاوا اسکریپتی). ضمنا این مطلب رو برای سؤال پرسیدن باید رعایت کنی: آناتومی یک گزارش خطای خوب . کسی نمی‌دونه این ارور می‌ده یعنی چی؟ چه خطایی می‌ده؟ کسی مونیتورت رو نمی‌تونه از راه دور ببینه. باید توضیح بدی. تشریحش کنی با کمک این ابزار: نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery        

مطالب دوره‌ها
پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک 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
 
نظرات مطالب
Url Routing در ASP.Net WebForms
این نوع routing خاص سمت کلاینت برنامه‌های تک صفحه‌ای وب را با استفاده از کتابخانه‌ای مانند path.js می‌شود انجام داد. مثال‌های آن‌را به همراه سورس صفحات آن بررسی کنید.
نمونه آن‌را در مطلب « پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC  » (کامنت آخر آن) پیاده سازی شده می‌توانید مشاهده و دریافت کنید. از این قابلیت در سایت جاری در حال استفاده است. مثلا اگر به قسمت مطالب در بالای صفحه مراجعه کنید، چنین آدرسی قابل مشاهده است:
https://www.dntips.ir/postsarchive#/page/1/date/desc  
در پایین صفحه اگر دراپ داون‌های مرتب سازی را تغییر دهید، نام فیلد یا صعودی و نزولی بودن آدرس تغییر می‌کنند. شماره صفحه نیز با هر بار کلیک بر روی دکمه‌ی بیشتر یکی اضافه می‌شود. همچنین این آدرس‌ها را می‌شود ذخیره و عینا بازیابی کرد.
نظرات مطالب
طریقه بررسی صحت کدملی به کمک متدهای الحاقی
یک حالت جدید پیدا کردم که مطمئنا خود ثبت احوال هم نمی‌دونه! هر عدد ده رقمی که به فرم قربنه وارد بشه، صحیح فرض میشه!
مثلا a00000000a  یا 1234554321 یا 1346886431 همگی صحیح فرض میشن حال آنکه اشتباه هستن!