مهدی عادلی فر
بنیانگذار توسینسو و برنامه نویس

آموزش استفاده از دیتاتیبل (DataTables) بصورت ServerSide

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

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران

این حجم بسیار زیاد داده ها باعث مصرف ترافیک زیاد و همچنین کاهش سرعت به دلیل انتقال داده ها می شود. راهکاری که برای این کار داده شده است این است که همه اعمال مرتب سازی و جستجو و صفحه بندی و سایر پردازش ها در سمت سرور انجام شود (server side proccessing) و داده های نتیجه به سمت کلاینت ارسال شوند. این کار با توجه به این که از امکانات datatables استفاده می کند باعث می شود که مشکل انتقال داده های زیاد به سمت کلاینت حل شود.

پردازش server side برای دیتاتیبل

Datatables این قابلیت را برای همه زبان های برنامه نویسی که قابلیت پردازش درخواست های وب را دارند در نظر گرفته است و اگر به سایت این پلاگین مراجعه کنید خواهید دید که مثال هایی برای زبان های مختلف وجود دارد. در این مطلب ما پردازش server side را با زبان سی شارپ و تکنولوژی asp.net core انجام می دهیم.

شروع کار

برای شروع کار یک پروژه Asp.net core mvc را در محیط ویژوال استودیو شروع می کنیم. همچنین از یک پکیج Nuget برای کار با دیتاتیبل استفاده می کنیم این پکیج اکثر کارهای پیچیده را در مورد استفاده از دیتاتیبل در سمت سرور را انجام داده است و کار را بسیار راحت تر می کند. این پکیج  JqueryDataTables.ServerSide.AspNetCoreWeb است که آن را بر روی پروژه نصب می کنیم. با استفاده از این پکیج attribute هایی اضافه می شود که از آنها برای مشخص کردن امکانات datatabels استفاده می کنیم. برای مثال کلاس demo را به شکل زیر تعریف کرده ایم.


public class Demo 

    { 

        public int Id { get; set; } 




        [SearchableString] 

        [Sortable(Default = true)] 

        public string Name { get; set; } 




        [SearchableString] 

        [Sortable] 

        public string Position { get; set; } 




        [SearchableString] 

        [Sortable] 

        public string Office { get; set; } 




        [SearchableShort] 

        [Sortable] 

        public short? Experience { get; set; } 




        [SearchableInt] 

        [Sortable] 

        [DisplayName("Extension")] 

        public int? Extn { get; set; } 




        [SearchableDateTime] 

        [Sortable] 

        [DisplayName("Start Date")] 

        public DateTime? StartDate { get; set; } 




        [SearchableLong] 

        [Sortable] 

        public long? Salary { get; set; } 
}

همانطور که در کد می بینید با استفاده از attribute هایی مانند searchable, sortable و همچنین نوع داده برای سرچ کردن امکانات مورد نظر را به هر ستون اضافه می کنیم. اگر بخواهیم یک ستون قابل مرتب سازی باشد از attribute به نام Sortable استفاده می کنیم و اگر از attribute به شکل [Sortable(Default = true)] استفاده کنیم وقتی که داده ها لود می شوند در شروع کار داده ها طبق این ستون مرتب شده اند.

همچنین برای اضافه کردن قابلیت سرچ به یک ستون از attribute به نام Searchable استفاده می شود. البته دقت کنید که attribute های جستجو چند تا هستند که لیست آنها در زیر آمده است.

  • [Searchable] 
  • [SearchableString]
  • [SearchableInt]
  • [SearchableShort]
  • [SearchableDecimal]
  • [SearchableDouble]
  • [SearchableDateTime]
  • [SearchableLong]

و در نهایت attribute به نام [DisplayName(“”)]  برای این است که نام ستون جدول را تغییر دهیم.

پیکربندی پروژه

در ابتدای پروژه پیکربندی و استفاده از سرویس ها را شروع می کنیم. برای این کار در فایل startup.cs که فایل پیکربندی سرویس ها است در بخش سرویس ها کد را به شکل زیر تغییر می دهیم.

services.AddMvc(); 

services.AddSession(); 

services.AddJqueryDataTables(); 

services.AddAutoMapper(typeof(Startup));

سرویس jqueryDatatabels برای کار با دیتاتیبل است و سرویس automapper برای این است که عملیات مپ کردن را انجام دهد. دقت کنید که برای این که برنامه شما خطا نداشته باشد باید پکیج های زیر را به پروژه اضافه کرده باشید.

  • AutoMapper
  • Extensions.Microsoft.DependencyInjection    

نوشتن فایل های سمت سرور

اگر شما در پروژه خود دیتابیس داشته باشید می توانید از آن استفاده کنید ولی در این مثال از دیتابیس in-memory استفاده می کنیم. به همین دلیل در سرویس ها از کد زیر استفاده می کنیم.

services.AddDbContext<ApplicationDbContext>( 

                options => 

                { 

                    options.UseInMemoryDatabase("appdb"); 

                }); 

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

public class ApplicationDbContext:DbContext 

    { 

       public ApplicationDbContext(DbContextOptions options)

            : base(options) { } 




        public DbSet<DemoEntity> Demos { get; set; } 

    } 

در کلاس بالا یک کلاس مدل به نام DemoEntity نام برده ایم این کلاس را در پوشه model به شکل زیر تعریف می کنیم.

public class DemoEntity 

    { 

        [Key] 

        public int Id { get; set; } 

        public string Name { get; set; } 

        public string Position { get; set; } 

        public string Office { get; set; } 

        public short? Experience { get; set; } 

        public int? Extn { get; set; } 

        public DateTime? StartDate { get; set; } 

        public long? Salary { get; set; } 

    } 

حال یک سری داده تعریف می کنیم که برای نمایش از آنها استفاده می کنیم برای این کار یک کلاس استاتیک به نام SeedData  را به شکل زیر تعریف می کنیم.

public static class SeedData 

    { 

        public static async Task InitializeAsync(IServiceProvider services) 

        { 

            await AddTestData( 

                services.GetRequiredService< ApplicationDbContext >()); 

        } 




        public static async Task AddTestData(ApplicationDbContext context) 

        { 

            if(context.Demos.Any()) 

            { 

                // Already has data 

                return; 

            } 




            var testData = new List<DemoEntity>() 

            { 

                new DemoEntity { 

                    Name = "Airi Satou", 

                    Position = "Accountant", 

                    Office = "Tokyo", 

                    Experience = null, 

                    Extn = null, 

                    StartDate = null, 

                    Salary = null 

                }, 

                new DemoEntity { 

                    Name = "Angelica Ramos", 

                    Position = "Chief Executive Officer (CEO)", 

                    Office = "London", 

                    Experience = 1, 

                    Extn = 5797, 

                    StartDate = new DateTime(2009,10,09), 

                    Salary = 1200000 

                }, 

                new DemoEntity { 

                    Name = "Ashton Cox", 

                    Position = "Junior Technical Author", 

                    Office = "San Francisco", 

                    Experience = 2, 

                    Extn = 1562, 

                    StartDate = new DateTime(2009,01,12), 

                    Salary = 86000 

                }, 

                new DemoEntity { 

                    Name = "Bradley Greer", 

                    Position = "Software Engineer", 

                    Office = "London", 

                    Experience = 3, 

                    Extn = 2558, 

                    StartDate = new DateTime(2012,10,13), 

                    Salary = 132000 

                }, 

                new DemoEntity { 

                    Name = "Brenden Wagner", 

                    Position = "Software Engineer", 

                    Office = "San Francisco", 

                    Experience = 4, 

                    Extn = 1314, 

                    StartDate = new DateTime(2011,06,07), 

                    Salary = 206850 

                }, 

                new DemoEntity { 

                    Name = "Brielle Williamson", 

                    Position = "Integration Specialist", 

                    Office = "New York", 

                    Experience = 5, 

                    Extn = 4804, 

                    StartDate = new DateTime(2012,12,02), 

                    Salary = 372000 

                }, 

                new DemoEntity { 

                    Name = "Bruno Nash", 

                    Position = "Software Engineer", 

                    Office = "London", 

                    Experience = 6, 

                    Extn = 6222, 

                    StartDate = new DateTime(2011,05,03), 

                    Salary = 163500 

                }, 

                new DemoEntity { 

                    Name = "Caesar Vance", 

                    Position = "Pre-Sales Support", 

                    Office = "New York", 

                    Experience = 7, 

                    Extn = 8330, 

                    StartDate = new DateTime(2011,12,12), 

                    Salary = 106450 

                }, 

                new DemoEntity { 

                    Name = "Cara Stevens", 

                    Position = "Sales Assistant", 

                    Office = "New York", 

                    Experience = 8, 

                    Extn = 3990, 

                    StartDate = new DateTime(2011,12,06), 

                    Salary = 145600 

                }, 

                new DemoEntity { 

                    Name = "Cedric Kelly", 

                    Position = "Senior Javascript Developer", 

                    Office = "Edinburgh", 

                    Experience = 9, 

                    Extn = 6224, 

                    StartDate = new DateTime(2012,03,29), 

                    Salary = 433060 

                } 

            }; 




            context.Demos.AddRange(testData); 




            await context.SaveChangesAsync(); 

        } 

    } 

حال باید هنگامی که برنامه شروع به کار می کند داده های تستی را پر کنیم برای این کار فایل Program.cs که تابع main برنامه در داخل آن قرار گرفته است را باز می کنیم و آن را به شکل زیر تغییر می دهیم.

  public class Program 

    { 

        public static void Main(string[] args) 

        { 

            var host = CreateWebHostBuilder(args).Build(); 

            InitializeDatabase(host); 

            host.Run(); 

        } 




        public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 

            WebHost.CreateDefaultBuilder(args) 

                .UseStartup<Startup>(); 




        private static void InitializeDatabase(IWebHost host) 

        { 

            using (var scope = host.Services.CreateScope())  

            { 

                var services = scope.ServiceProvider; 




                try 

                { 

                    SeedData.InitializeAsync(services).Wait(); 

                } 

                catch (Exception ex) 

                { 

                    var logger = services.GetRequiredService<ILogger<Program>>(); 

                    logger.LogError(ex, "An error occurred seeding the database."); 

                } 

            } 

        } 

    } 

انجام کار در سمت کلاینت و ویو

حال  زمان استفاده از دیتاتیبل است. فایل های مربوط به دیتاتیبل را که در مطلب قبل توضیح دادیم را به پروژه و به پوشه wwwroot اضافه می کنیم. همچنین لینک فایل های css,  و جاوا اسکریپت را به _Layout.cshtml نیز اضافه می کنیم.

در قدم بعدی فایل _ViewImports.cshtml را به شکل زیر تغییر میدهیم که بتواند datatables را بشناسد.

@addTagHelper *, JqueryDataTables.ServerSide.AspNetCoreWeb

حال می توانید در ویویی که می خواهید اطلاعات را نمایش دهید از تگ jquery-datatables به شکل زیر استفاده کنید.

<jquery-datatables id="appTable" 

                   class="table table-sm table-dark table-bordered table-hover" 

                   model="@Model" 

                   thead-class="text-center" 

                   enable-searching="true" 

                   search-row-th-class="p-0" 

                   search-input-class="form-control form-control-sm"> 

</jquery-datatables>

مشخصاتی که برای این تگ قرار داده شده است عبارتند از:

  • Id برای اضافه کردن id به تگ table
  • Class برای مشخص کردن کلاس css مربوط به table
  • Model برای مشخص کردن مدل داده ها و ستون های جدول
  • Thead-class برای این که کلاس thead را مشخص کنیم
  • Enable-searchign برای اضافه کردن قابلیت سرچ به thead
  • Search-row-th-class برای مشخص کردن کلاس مربوط به Input جستجو در thead
  • Search-input-class برای مشخص کردن کلاس css مربوط به search input

کد بالا جدول را بر اساس اطلاعات و کلاس های داده شده می سازد. حال باید دیتاتیبل را با استفاده از جاوا اسکریپت اعمال کنیم. دقت کنید که serverSide را برابر true قرار داده ایم که پردازش در سمت سرور انجام شود و orderCellsTop نیز آیکن مرتب سازی را بالای هر ستون قرار می دهد. کد به شکل زیر خواهد بود.

var table = $('#appTable').DataTable({ 

            language: { 

                processing: "Loading Data...", 

                zeroRecords: "No matching records found" 

            }, 

            processing: true, 

            serverSide: true, 

            orderCellsTop: true, 

            autoWidth: true, 

            deferRender: true, 

            lengthMenu: [5, 10, 15, 20], 

            dom: '<"row"<"col-sm-12 col-md-6"B><"col-sm-12 col-md-6 text-right"l>><"row"<"col-sm-12"tr>><"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>', 

           

            ajax: { 

                type: "POST", 

                url: '/Home/LoadTable/', 

                contentType: "application/json; charset=utf-8", 

                async: true, 

                headers: { 

                    "XSRF-TOKEN": document.querySelector('[name="__RequestVerificationToken"]').value 

                }, 

                data: function (data) { 

                    let additionalValues = []; 

                    additionalValues[0] = "Additional Parameters 1"; 

                    additionalValues[1] = "Additional Parameters 2"; 

                    data.AdditionalValues = additionalValues; 




                    return JSON.stringify(data); 

                } 

            }, 

            columns: [ 

                ...

            ] 

        }); 

در کد بالا additionalValues مقادیر دلخواهی است که می توان برای ارسال اطلاعات دلخواه از آن استفاده کرد و در صورتی که نیاز ندارید می توانید آنها را حذف کنید.

بخش آرایه columns به صورت زیر است که هر ستون را به صورت جدا گانه پیکربندی کرده ایم.

columns: [ 

                { 

                    data: "Id", 

                    visible: false, 

                    searchable: false 

                }, 

                { 

                    data: "Name", 

                }, 

                { 

                    data: "Position", 

                }, 

                { 

                    data: "Office", 

                }, 

                { 

                    data: "Experience", 

                }, 

                { 

                    data: "Extn", 

                }, 

                { 

                    data: "StartDate", 

                    render: function (data, type, row) { 

                        if (data) 

                            return window.moment(data).format("DD/MM/YYYY"); 

                        else 

                            return null; 

                    }, 

                }, 

                { 

                    data: "Salary", 

                } 

            ] 

مقدار data باید دقیقا برابر آن ستونی باشد که قرار است نمایش داده شود .

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

table.columns().every(function (index) { 

            $('#appTable thead tr:last th:eq(' + index + ') input') 

                .on('keyup', 

                    function (e) { 

                        if (e.keyCode === 13) { 

                            table.column($(this).parent().index() + ':visible').search(this.value).draw(); 

                        } 

                    }); 

        });

نوشتن کدهای Controller

داده های دیتاتیبل توسط درخواست Post برای controller ارسال می شود. بنابراین باید در controller مربوطه کد زیر را بنویسیم.

private readonly IDemoService _demoService;

        private readonly IMapper _mapper;




        public HomeController(IDemoService demoService, IMapper mapper)

        {

            _demoService = demoService;

            _mapper = mapper;

        }

[HttpPost]   

        public async Task<IActionResult> LoadTable([FromBody]JqueryDataTablesParameters param)   

        {   

            try   

            {   

                HttpContext.Session.SetString(nameof(JqueryDataTablesParameters),JsonConvert.SerializeObject(param));   

                var results = await _demoService.GetDataAsync(param);   

   

                return new JsonResult(new JqueryDataTablesResult<Demo> {   

                    Draw = param.Draw,   

                    Data = results.Items,   

                    RecordsFiltered = results.TotalSize,   

                    RecordsTotal = results.TotalSize   

                });   

            } catch(Exception e)   

            {   

                Console.Write(e.Message);   

                return new JsonResult(new { error = "Internal Server Error" });   

            }   

        }  

در کد بالا از demoService استفاده شده است. کلاس  DemoService را به شکل زیر تعریف کرده ایم کهک بتواند عمل مپ کردن را انجام دهد و همچنین بتواند با داده ها کار کند.

public class DefaultDemoService:IDemoService 

    { 

        private readonly ApplicationDbContext  _context; 

        private readonly IConfigurationProvider _mappingConfiguration; 




        public DefaultDemoService(ApplicationDbContext context,IConfigurationProvider mappingConfiguration) 

        { 

            _context = context; 

            _mappingConfiguration = mappingConfiguration; 

        } 




        public async Task<JqueryDataTablesPagedResults<Demo>> GetDataAsync(JqueryDataTablesParameters table) 

        { 

            IQueryable<DemoEntity> query = _context.Demos; 

            query = new SearchOptionsProcessor<Demo,DemoEntity>().Apply(query,table.Columns); 

            query = new SortOptionsProcessor<Demo,DemoEntity>().Apply(query,table); 




            var size = await query.CountAsync(); 




            var items = await query 

                .AsNoTracking() 

                .Skip((table.Start / table.Length) * table.Length) 

                .Take(table.Length) 

                .ProjectTo<Demo>(_mappingConfiguration) 

                .ToArrayAsync(); 




            return new JqueryDataTablesPagedResults<Demo> { 

                Items = items, 

                TotalSize = size 

            }; 

        } 

    } 

در کد بالا کوئری های مربوط به مرت سازی و جستجو و صفحه بندی نیز نوشته شده است. حال اگر اجرا کنید می بیندی که جدول به صورت داینامیک کار می کند و پردازش ها در سمت سرور انجام می شود. کد کامل این پروژه را می توانید از این لینک دریافت کنید.


مهدی عادلی فر
مهدی عادلی فر

بنیانگذار توسینسو و برنامه نویس

مهدی عادلی، بنیان گذار TOSINSO. کارشناس ارشد نرم افزار کامپیوتر از دانشگاه صنعتی امیرکبیر و #C و جاوا و اندروید کار می کنم. در زمینه های موبایل و وب و ویندوز فعالیت دارم و به طراحی نرم افزار و اصول مهندسی نرم افزار علاقه مندم.

نظرات