ASP.NET 2.0 introduced a concept of application offline. This mean that when there is App_Offline.htm file in the root of a web application directory then ASP.NET will shut-down the application, unload the application domain from the server, and stop processing any new incoming requests for that application. In ASP.NET 5, there is an open-issue for supporting this feature.
در ابتدا نیاز است که بستههای زیر را از Nuget دریافت و نصب کنیم:
PM>Install-Package Autofac PM>Install-Package Autofac.Mvc5 PM>Install-Package AutoMapper
حال در این بخش به تعاریف داخلی پروژه میپردازیم:
namespace AufacDI.DomainClasses { public class Category { public int Id { get; set; } public string Name { get; set; } } }
namespace AufacDI.ViewModel { public class CategoryViewModel { public int Id { get; set; } public int Name { get; set; } } }
using AufacDI.DomainClasses; using AufacDI.ViewModel; using AutoMapper; namespace AufacDI.MapperProfile { public class CategoryProfile : Profile { public CategoryProfile() { CreateMap<Category, CategoryViewModel>(); CreateMap<CategoryViewModel, Category>(); } } }
using AufacDI.MapperProfile; using Autofac; using AutoMapper; using System; using System.Linq; namespace AufacDI.IocConfig { public static class IoCContainer { public static void Register(ContainerBuilder builder) { // شناسایی پروفایلها براساس نمونه از کلاس پر.وفایل var profiles = from types in typeof(CategoryProfile).Assembly.GetTypes() where typeof(Profile).IsAssignableFrom(types) select (Profile)Activator.CreateInstance(types); // رجیستر کردن کلاسهای پروفایل در اتومپر builder.Register(ctx => new MapperConfiguration(cfg => { foreach (var profile in profiles) cfg.AddProfile(profile); })).SingleInstance().AutoActivate().AsSelf(); // رجیستر کردن کلاس MapperConfiguration و ایجاد آن براساس IMapper builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper()).As<IMapper>().InstancePerRequest(); } } }
var builder = new ContainerBuilder(); // تزریق کنترلرها برای تزریف سایر المانها در سازنده builder.RegisterControllers(typeof(MvcApplication).Assembly).InstancePerDependency(); // فراخوانی متد رجیستر برای تزریق وابستگی مپر و کلاسهای پروفایل آن IoCContainer.Register(builder); // ایجاد نمونه از سازنده var container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
namespace AufacDI.WebApplication.Controllers { public class HomeController : Controller { private readonly IMapper _mapepr; public HomeController(IMapper mapepr) { _mapepr = mapepr; } public ActionResult Index() { // مپ کردن یک کلاس به یک کلاس var categoryViewModel = new CategoryViewModel { Id = 1, Name = "News" }; var categoryModel = _mapepr.Map<CategoryViewModel, Category>(categoryViewModel); // مپ کردن لیست از کلاس به لیستی از کلاس var categoryListModel = new List<Category>(); categoryListModel.Add(new Category { Id = 1, Name = "A" }); categoryListModel.Add(new Category { Id = 2, Name = "B" }); categoryListModel.Add(new Category { Id = 3, Name = "C" }); categoryListModel.Add(new Category { Id = 4, Name = "D" }); categoryListModel.Add(new Category { Id = 5, Name = "E" }); var categoryListViewModel = categoryListModel.AsQueryable().ProjectTo<CategoryViewModel>(_mapepr.ConfigurationProvider).ToList(); ; return View(); } } }
Unit Testing در AngularJS
JavaScript is a dynamically typed language which comes with great power of expression, but it also comes with almost no help from the compiler. For this reason we feel very strongly that any code written in JavaScript needs to come with a strong set of tests. We have built many features into Angular which make testing your Angular applications easy. With Angular, there is no excuse for not testing.
var container = new Container(x => { x.Scan(scanner => { scanner.AssemblyContainingType<IOrderHandler>(); // connects `IAccounting` to `Accounting` and `ISales` to `Sales` automatically. scanner.WithDefaultConventions(); }); });
builder.RegisterAssemblyTypes(myAssembly) .Where(t => t.IsAssignableTo<IMyInterface>()) .AsImplementedInterfaces();
دریافت و نصب کتابخانهی کمکی Scrutor
کتابخانهی کمکی Scrutor سورس باز بوده و بستهی NuGet آن توسط یکی از دستورات زیر به پروژه افزوده میشود:
> Install-Package Scrutor > dotnet add package Scrutor
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="Scrutor" Version="3.0.2" /> </ItemGroup> </Project>
ثبت و معرفی سادهتر سرویسها بر اساس قواعد نامگذاری آنها توسط Scrutor
فرض کنید تعدادی سرویس را به صورت زیر تعریف کردهاید:
namespace CoreIocServices { public interface IFoo { void Run(); } public class Foo : IFoo { public void Run() { throw new System.NotImplementedException(); } } public interface IBar { void Add(); } public class Bar : IBar { public void Add() { throw new System.NotImplementedException(); } } public interface IBaz { void Stop(); } public class Baz : IBaz { public void Stop() { throw new System.NotImplementedException(); } } }
services.AddScoped<IFoo, Foo>(); services.AddScoped<IBar, Bar>(); services.AddScoped<IBaz, Baz>();
در اینجا در حین تعریف سرویسهای فوق این روش نامگذاری رعایت شدهاست: هر اینترفیس، نامش یک I بیشتر از نام کلاس مشتق شدهی از آن دارد؛ مانند اینترفیس IFoo و کلاس Foo. کتابخانهی StructureMap که در ابتدای بحث معرفی شد، کار اسکن و اتصال یک چنین سرویسهایی را با تعریف scanner.WithDefaultConventions انجام میدهد. معادل آن با Scrutor به صورت زیر است:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.Scan(scan => //scan.FromCallingAssembly() scan.FromAssemblyOf<IFoo>() .AddClasses() .AsMatchingInterface() .WithScopedLifetime());
- scan.FromAssemblyOf کار اسکن اسمبلی را انجام میدهد که نوع IFoo در آن قرار دارد. اگر از scan.FromCallingAssembly استفاده کنیم، به این معنا است که کار اسکن را دقیقا از همین اسمبلی فراخوان کدهای جاری، شروع کن. اما چون IFoo تعریف شده، در یک پروژه و اسمبلی دیگر قرار دارد، به همین جهت نیاز به ذکر صریح اسمبلی آن نیز هست.
- AddClasses یعنی تمام کلاسهای public, non-abstract را به لیست services اضافه کن.
- AsMatchingInterface یعنی بر اساس قرارداد نامگذاری IClassName و ClassName، اتصالات سرویسها را انجام بده.
بجای آن میتوان از AsImplementedInterfaces نیز استفاده کرد. این حالت برای زمانی مناسب است که یک کلاس، چندین اینترفیس را پیاده سازی کند (مثلا کلاس TestService اینترفیسهای ITestService و IService را پیاده سازی کرده باشد) و علاقمند باشید به ازای هر اینترفیس، یکبار سرویس آن نیز ثبت شود؛ کاری مانند تنظیمات زیر:
services.AddScoped<ITestService, TestService>(); services.AddScoped<IService, TestService>();
- WithScopedLifetime نیز طول عمر این سرویسهای اضافه شده را مشخص میکند. در اینجا میتوان WithTransientLifetime و WithSingletonLifetime را نیز ذکر کرد.
بنابراین همانطور که ملاحظه میکنید، هنوز هم همان سیستم Microsoft.Extensions.DependencyInjection برقرار است؛ اما با وجود متد الحاقی جدید Scan، کار تعاریف سرویسهای برنامه به شدت ساده میشود.
کار با وهلههای کلاسهای سرویسها بجای اینترفیسهای آن توسط Scrutor
میخواهیم مثال سوم قسمت ششم «چگونه بجای اینترفیسها، یک وهله از کلاسی مشخص را از سیستم تزریق وابستگیها درخواست کنیم؟» را توسط Scrutor پیاده سازی کنیم:
namespace CoreIocServices { public interface IService { } public class Service1 : IService { } public class Service2 : IService { } public class Service : IService { } }
services.AddTransient<Service1>(); services.AddTransient<Service2>(); services.AddTransient<Service>();
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.Scan(scan => //scan.FromCallingAssembly() scan.FromAssemblyOf<IService>() .AddClasses() .AsSelf() .WithTransientLifetime());
services.Scan(scan => scan.AddTypes(new[] { typeof(Service1), typeof(Service2) }) .AsSelf() .WithTransientLifetime());
AsSelf: معادل ()<services.AddTransient<TestService است. در این حالت کلاسهایی که اینترفیسی را پیاده سازی نمیکنند و یا در کل مایل هستید که از طریق تزریق وابستگیها در دسترس باشند، میتوان توسط متد AsSelf به سیستم معرفی کرد.
AsSelfWithInterfaces: معادل تنظیمات زیر است:
services.AddSingleton<TestService>(); services.AddSingleton<ITestService>(x => x.GetRequiredService<TestService>()); services.AddSingleton<IService>(x => x.GetRequiredService<TestService>());
روشهای متفاوت اسکن اسمبلیها در Scrutor
Scrutor به همراه روشهای متعددی برای تعریف اسمبلی یا اسمبلیهایی است که باید اسکن شوند و نمونهای از آنرا با FromAssemblyOf بررسی کردیم:
services.Scan(scan => //scan.FromCallingAssembly() scan.FromAssemblyOf<IService>()
الف) FromAssemblyOf<>, FromAssembliesOf : اسمبلی یا اسمبلیهایی که نوع یا نوعهای تعیین شده را به همراه دارند، اسکن میکند.
ب) FromCallingAssembly, FromExecutingAssembly, FromEntryAssembly کار اسکن اسمبلیهای فراخوان، اسمبلی که هم اکنون در حال اجرا است و اسمبلی آغازین برنامه را انجام میدهند.
ج) FromAssemblyDependencies: تمام اسمبلیهایی را که وابستهی به اسمبلی معرفی شدهی به آن هستند، اسکن میکند.
د) FromApplicationDependencies, FromDependencyContext: تمام اسمبلیهایی را که توسط برنامه، ارجاعی به آنها وجود دارند، اسکن میکند.
انتخاب دقیقتر کلاسها و سرویسهای مدنظر توسط Scrutor
شاید عملکرد کلی متد AddClasses مدنظر شما نباشد و نیاز به انتخاب دقیقتری از سرویسهای اسکن شده را داشته باشید؛ برای این مورد نیز Scrutor روشهای زیر را ارائه میدهد. برای مثال خود کلاس AddClasses دارای overloadهای زیر نیز هست:
public interface IImplementationTypeSelector : IAssemblySelector, IFluentInterface { IServiceTypeSelector AddClasses(); IServiceTypeSelector AddClasses(bool publicOnly); IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action); IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action, bool publicOnly); }
services.Scan(scan => scan .FromAssemblyOf<IService>() .AddClasses(classes => classes.AssignableTo<IService>()) // .AddClasses(classes => classes.InNamespaces("MyApp")) // .AddClasses(classes => classes.Where(type => type.Name.EndsWith("Repository")) .AsImplementedInterfaces() .WithTransientLifetime());
مدیریت جایگزینی سرویسها توسط Scrutor
یکی از مزیتهای طراحی یک برنامه با درنظر گرفتن الگوی تزریق وابستگیها، امکان جایگزین کردن سرویسهای پیشفرض آن با سرویسهای دیگری است. فرض کنید کتابخانهای ارائه شده و از الگوریتم هش کردن X استفاده کردهاست؛ اما شما علاقمندید تا از الگوریتم Y بجای آن استفاده کنید. اگر این کتابخانه وهلهی الگوریتم هش کردن را از طریق تزریق وابستگیها تامین کرده باشد، فقط کافی است در ابتدای معرفی تنظیمات تزریق وابستگیهای آن، سرویس الگوریتم هش کردن موجود را با نمونهی خاص خودتان جایگزین کنید.
اکنون فرض کنید پیش از استفادهی از Scrutor، تعدادی سرویس را به روش متداولی ثبت و معرفی کردهاید:
services.AddTransient<ITransientService, TransientService>(); services.AddScoped<IScopedService, ScopedService>();
public class TransientService : IFooService {} public class AnotherService : IScopedService {}
services.Scan(scan => scan.FromAssemblyOf<IFoo>() .AddClasses() .UsingRegistrationStrategy(RegistrationStrategy.Skip) .AsMatchingInterface() .WithScopedLifetime());
namespace Scrutor { public abstract class RegistrationStrategy { public static readonly RegistrationStrategy Skip; public static readonly RegistrationStrategy Append; protected RegistrationStrategy(); public static RegistrationStrategy Replace(); public static RegistrationStrategy Replace(ReplacementBehavior behavior); public abstract void Apply(IServiceCollection services, ServiceDescriptor descriptor); } }
- حالت Skip آن، سرویسی را تکراری ثبت نمیکند. یعنی اگر سرویسی پیشتر در مجموعهی IServiceCollection موجود بود، مجددا آنرا ثبت نمیکند.
سپس نوبت به متدهای Replace میرسد که یک چنین پارامتری را قبول میکنند:
namespace Scrutor { [Flags] public enum ReplacementBehavior { Default = 0, ServiceType = 1, ImplementationType = 2, All = 3 } }
- در حالت استفادهی از Replace(ReplacementBehavior.ImplementationType)، اگر پیاده سازی کلاسی پیشتر در لیست IServiceCollection ثبت شده باشد، آنرا حذف کرده و سپس نمونهی جدید را ثبت میکند (ثبت سرویس صرفا بر اساس نام کلاس آن).
- حالت Replace(ReplacementBehavior.All) هر دو حالت قبل را با هم شامل میشود.
امکان ترکیب چندین استراتژی جستجو با هم توسط Scrutor
در یک برنامهی واقعی غیرممکن است که بخواهید تمام کلاسها را با یک طول عمر، اسکن و ثبت کنید. برای این منظور میتوان از قابلیت فیلتر کردن کلاسها که در مورد آن بحث شد و همچنین امکان ترکیب زنجیر وار حالتهای مختلف اسکن، استفاده کرد:
services.Scan(scan => scan .FromAssemblyOf<CombinedService>() .AddClasses(classes => classes.AssignableTo<ICombinedService>()) // Filter classes .AsSelfWithInterfaces() .WithSingletonLifetime() .AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types .AsMatchingInterface() .AddClasses(x=> x.InNamespaceOf<MyClass>()) .UsingRegistrationStrategy(RegistrationStrategy.Replace()) // Defaults to ReplacementBehavior.ServiceType .AsMatchingInterface() .WithScopedLifetime() .FromAssemblyOf<DatabaseContext>() // Can load from multiple assemblies within one Scan() .AddClasses() .AsImplementedInterfaces() );
سری ویدیوهای رسمی Migrations از تیم EF
Install-Package EntityFramework -Version 6.1.0-beta1
مجوز استفاده از فایلهای جاوا اسکریپتی آن MIT است؛ به این معنا که در هر نوع پروژهای قابل استفاده است. مجوز استفاده از کامپوننتهای سمت سرور آن که برای نمونه جهت ASP.NET MVC یک سری HTML Helper را تدارک دیدهاند، تجاری میباشد. در ادامه قصد داریم صرفا از فایلهای JS عمومی آن استفاده کنیم.
دریافت jqGrid
برای دریافت jqGrid میتوانید به مخزن کد آن، در آدرس https://github.com/tonytomov/jqGrid/releases و یا از طریق NuGet اقدام کنید:
PM> Install-Package Trirand.jqGrid
از jQuery UI برای تولید صفحات جستجوی بر روی رکوردها و همچنین تولید خودکار صفحات ویرایش و یا افزودن رکوردها استفاده میکند. به علاوه آیکنها، قالب و رنگ خود را نیز از jQuery UI دریافت میکند. بنابراین اگر قصد تغییر قالب آنرا داشتید تنها کافی است یک قالب استاندارد دیگر jQuery UI را مورد استفاده قرار دهید.
تنظیمات اولیه فایل Layout سایت
پس از دریافت بستهی نیوگت jqGrid، نیاز است فایلهای مورد نیاز اصلی آنرا به شکل زیر به فایل layout پروژه اضافه کرد:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - My ASP.NET Application</title> <link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" /> <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" /> <link href="~/Content/Site.css" rel="stylesheet" type="text/css" /> </head> <body> <div> @RenderBody() </div> <script src="~/Scripts/jquery-1.7.2.min.js"></script> <script src="~/Scripts/jquery-ui-1.8.11.min.js"></script> <script src="~/Scripts/i18n/grid.locale-fa.js"></script> <script src="~/Scripts/jquery.jqGrid.min.js"></script> @RenderSection("Scripts", required: false) </body> </html>
این گرید به همراه فایل زبان فارسی grid.locale-fa.js نیز میباشد که در کدهای فوق پیوست شدهاست. البته اگر فرصت کردید نیاز است کمی ترجمههای آن بهبود پیدا کنند.
تنظیمات ثانویه site.css
.ui-widget { } /*how to move jQuery dialog close (X) button from right to left*/ .ui-jqgrid .ui-jqgrid-caption-rtl { text-align: center !important; } .ui-dialog .ui-dialog-titlebar-close { left: .3em !important; } .ui-dialog .ui-dialog-title { margin: .1em 0 .1em .8em !important; direction: rtl !important; float: right !important; }
همچنین محل قرار گیری دکمهی بسته شدن دیالوگها و راست به چپ کردن عناوین آنها نیز در اینجا قید شدهاند.
مدل برنامه
در ادامه قصد داریم لیستی از محصولات را با ساختار ذیل، توسط jqGrid نمایش دهیم:
namespace jqGrid01.Models { public class Product { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsAvailable { set; get; } } }
ساختار دادهای مورد نیاز توسط jqGrid
jqGrid مستقل است از فناوری سمت سرور. بنابراین هر چند در عنوان بحث ASP.NET MVC ذکر شدهاست، اما از ASP.NET MVC صرفا جهت بازگرداندن خروجی JSON استفاده خواهیم کرد و این مورد در هر فناوری سمت سرور دیگری نیز میتواند انجام شود.
using System.Collections.Generic; namespace jqGrid01.Models { public class JqGridData { public int Total { get; set; } public int Page { get; set; } public int Records { get; set; } public IList<JqGridRowData> Rows { get; set; } public object UserData { get; set; } } public class JqGridRowData { public int Id { set; get; } public IList<string> RowCells { set; get; } } }
Total، نمایانگر تعداد صفحات اطلاعات است. عدد Page، شماره صفحهی جاری است. عدد Records، تعداد کل رکوردهای گزارش را مشخص میکند. ساختار ردیفهای آن نیز تشکیل شدهاست از یک Id به همراه سلولهایی که باید با فرمت string، بازگشت داده شوند.
UserData اختیاری است. برای مثال اگر خواستید جمع کل صفحه را در ذیل گرید نمایش دهید، میتوانید یک anonymous object را در اینجا مقدار دهی کنید. خاصیتهای آن دقیقا باید با نام خاصیتهای ستونهای متناظر، یکی باشند. برای مثال اگر میخواهید عددی را در ستون Id، در فوتر گرید نمایش دهید، باید نام خاصیت را Id ذکر کنید.
کدهای سمت کلاینت گرید
در اینجا کدهای کامل سمت کلاینت گرید را ملاحظه میکنید:
@{ ViewBag.Title = "Index"; } <div dir="rtl" align="center"> <div id="rsperror"></div> <table id="list" cellpadding="0" cellspacing="0"></table> <div id="pager" style="text-align:center;"></div> </div> @section Scripts { <script type="text/javascript"> $(document).ready(function () { $('#list').jqGrid({ caption: "آزمایش اول", //url from wich data should be requested url: '@Url.Action("GetProducts","Home")', //type of data datatype: 'json', jsonReader: { root: "Rows", page: "Page", total: "Total", records: "Records", repeatitems: true, userdata: "UserData", id: "Id", cell: "RowCells" }, //url access method type mtype: 'GET', //columns names colNames: ['شماره', 'نام محصول', 'موجود است', 'قیمت'], //columns model colModel: [ { name: 'Id', index: 'Id', align: 'right', width: 50, sorttype: "number" }, { name: 'Name', index: 'Name', align: 'right', width: 300 }, { name: 'IsAvailable', index: 'IsAvailable', align: 'center', width: 100, formatter: 'checkbox' }, { name: 'Price', index: 'Price', align: 'center', width: 100, sorttype: "number" } ], //pager for grid pager: $('#pager'), //number of rows per page rowNum: 10, rowList: [10, 20, 50, 100], //initial sorting column sortname: 'Id', //initial sorting direction sortorder: 'asc', //we want to display total records count viewrecords: true, altRows: true, shrinkToFit: true, width: 'auto', height: 'auto', hidegrid: false, direction: "rtl", gridview: true, rownumbers: true, footerrow: true, userDataOnFooter: true, loadComplete: function() { //change alternate rows color $("tr.jqgrow:odd").css("background", "#E0E0E0"); }, loadError: function(xhr, st, err) { jQuery("#rsperror").html("Type: " + st + "; Response: " + xhr.status + " " + xhr.statusText); } //, loadonce: true }) .jqGrid('navGrid', "#pager", { edit: false, add: false, del: false, search: false, refresh: true }) .jqGrid('navButtonAdd', '#pager', { caption: "تنظیم نمایش ستونها", title: "Reorder Columns", onClickButton: function() { jQuery("#list").jqGrid('columnChooser'); } }); }); </script> }
Div سومی با id مساوی rsperror نیز تعریف شدهاست که از آن جهت نمایش خطاهای بازگشت داده شده از سرور استفاده کردهایم.
- در ادامه نحوهی فراخوانی افزونهی jqGrid را بر روی جدول list ملاحظه میکنید.
- خاصیت caption، عنوان نمایش داده شده در بالای گرید را مقدار دهی میکند:
- خاصیت url، به آدرسی اشاره میکند که قرار است ساختار JqGridData ایی را که پیشتر در مورد آن بحث کردیم، با فرمت JSON بازگشت دهد. در اینجا برای مثال به یک اکشن متد کنترلری در یک پروژهی ASP.NET MVC اشاره میکند.
- datatype را برابر json قرار دادهایم. از نوع xml نیز پشتیبانی میکند.
- شیء jsonReader را از این جهت مقدار دهی کردهایم تا بتوانیم شیء JqGridData را با اصول نامگذاری دات نت، هماهنگ کنیم. برای درک بهتر این موضوع، فایل jquery.jqGrid.src.js را باز کنید و در آن به دنبال تعریف jsonReader بگردید. به یک چنین مقادیر پیش فرضی خواهید رسید:
ts.p.jsonReader = $.extend(true,{ root: "rows", page: "page", total: "total", records: "records", repeatitems: true, cell: "cell", id: "id", userdata: "userdata", subgrid: {root:"rows", repeatitems: true, cell:"cell"} },ts.p.jsonReader);
- در ادامه mtype به GET تنظیم شدهاست. در اینجا مشخص میکنیم که عملیات Ajax ایی دریافت اطلاعات از سرور توسط GET انجام شود یا برای مثال توسط POST.
- خاصیت colNames، معرف نام ستونهای گرید است. برای اینکه این نامها از راست به چپ نمایش داده شوند، باید خاصیت direction به rtl تنظیم شود.
- colModel آرایهای است که تعاریف ستونها را در بر دارد. مقدار name آن باید یک نام منحصربفرد باشد. از این نام در حین جستجو یا ویرایش اطلاعات استفاده میشود. مقدار index نامی است که جهت مرتب سازی اطلاعات، به سرور ارسال میشود. تنظیم sorttype در اینجا مشخص میکند که آیا به صورت پیش فرض، ستون جاری رشتهای مرتب شود یا اینکه برای مثال عددی پردازش گردد. مقادیر مجاز آن text (مقدار پیش فرض)، float، number، currency، numeric، int ، integer، date و datetime هستند.
- در ستون IsAvailable، مقدار formatter نیز تنظیم شدهاست. در اینجا توسط formatter، نوع bool دریافتی با یک checkbox نمایش داده خواهد شد.
- خاصیت pager به id متناظری در صفحه اشاره میکند.
- توسط rowNum مشخص میکنیم که در هر صفحه چه تعداد رکورد باید نمایش داده شوند.
- تعداد رکوردهای نمایش داده شده را میتوان توسط rowList پویا کرد. در اینجا آرایهای را ملاحظه میکنید که توسط اعداد آن، کاربر امکان انتخاب صفحاتی مثلا 100 ردیفه را نیز پیدا میکند. rowList به صورت یک dropdown در کنار عناصر راهبری صفحه در فوتر گرید ظاهر میشود.
- خاصیت sortname، نحوهی مرتب سازی اولیه گرید را مشخص میکند.
- خاصیت sortorder، جهت مرتب سازی اولیهی گردید را تنظیم میکند.
- viewrecords: تعداد رکوردها را در نوار ابزار پایین گرید نمایش میدهد.
- altRows: سبب میشود رنگ متن ردیفها یک در میان متفاوت باشد.
- shrinkToFit: به معنای تنظیم خودکار اندازهی سلولها بر اساس اندازهی دادهای است که دریافت میکنند.
- width: عرض گرید، که در اینجا به auto تنظیم شدهاست.
- height: طول گرید، که در اینجا به auto جهت محاسبهی خودکار، تنظیم شدهاست.
- gridview: برای بالا بردن سرعت نمایشی به true تنظیم شدهاست. در این حالت کل ردیف یکباره درج میشود. اگر از subgird یا حالت نمایش درختی استفاده شود، باید این خاصیت را false کرد.
- rownumbers: ستون سمت راست شماره ردیفهای خودکار را نمایش میدهد.
- footerrow: سبب نمایش ردیف فوتر میشود.
- userDataOnFooter: سبب خواهد شد تا خاصیت UserData مقدار دهی شده، در ردیف فوتر ظاهر شود.
- loadComplete : یک callback است که زمان پایان بارگذاری صفحهی جاری را مشخص میکند. در اینجا با استفاده از jQuery سبب شدهایم تا رنگ پس زمینهی ردیفها یک در میان تغییر کند.
- loadError: اگر از سمت سرور خطایی صادر شود، در این callback قابل دریافت خواهد بود.
- در ادامه توسط فراخوانی متد jqGrid با پارامتر navGrid، در ناحیه pager سبب نمایش دکمه refresh شدهایم. این دکمه سبب بارگذاری مجدد اطلاعات گردید از سرور میشود.
- همچنین به کمک متد jqGrid با پارامتر navButtonAdd در ناحیه pager، سبب نمایش دکمهای که صفحهی انتخاب ستونها را ظاهر میکند، خواهیم شد.
پیشنیاز کدهای سمت سرور jqGrid
اگر به تنظیمات گرید دقت کرده باشید، خاصیت index ستونها، نامی است که به سرور، جهت اطلاع رسانی در مورد فیلتر اطلاعات و مرتب سازی مجدد آنها ارسال میگردد. این نام، بر اساس کلیک کاربر بر روی ستونهای موجود، هر بار میتوان متفاوت باشد. بنابراین بجای if و else نوشتنهای طولانی جهت مرتب سازی اطلاعات، میتوان از کتابخانهی معروفی به نام dynamic LINQ استفاده کرد.
PM> Install-Package DynamicQuery
کدهای سمت سرور بازگشت اطلاعات به فرمت JSON
در کدهای سمت کلاینت، به اکشن متد GetProducts اشاره شده بود. تعاریف کامل آنرا در ذیل مشاهده میکنید:
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Web.Mvc; using jqGrid01.Models; using jqGrid01.Extensions; // for dynamic OrderBy namespace jqGrid01.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult GetProducts(string sidx, string sord, int page, int rows) { var list = ProductDataSource.LatestProducts; var pageIndex = page - 1; var pageSize = rows; var totalRecords = list.Count; var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize); var products = list.AsQueryable() .OrderBy(sidx + " " + sord) .Skip(pageIndex * pageSize) .Take(pageSize) .ToList(); var jqGridData = new JqGridData { UserData = new // نمایش در فوتر { Name = "جمع صفحه", Price = products.Sum(x => x.Price) }, Total = totalPages, Page = page, Records = totalRecords, Rows = (products.Select(product => new JqGridRowData { Id = product.Id, RowCells = new List<string> { product.Id.ToString(CultureInfo.InvariantCulture), product.Name, product.IsAvailable.ToString(), product.Price.ToString(CultureInfo.InvariantCulture) } })).ToList() }; return Json(jqGridData, JsonRequestBehavior.AllowGet); } } }
- امضای متد GetProducts نیز مهم است. دقیقا همین پارامترها با همین نامها از طرف jqGrid به سرور ارسال میشوند که توسط آنها ستون مرتب سازی، جهت مرتب سازی، صفحهی جاری و تعداد ردیفی که باید بازگشت داده شوند، قابل دریافت است.
- در این کدها دو قسمت مهم وجود دارند:
الف) متد OrderBy نوشته شده، به صورت پویا عمل میکند و از کتابخانهی Dynamic LINQ مایکروسافت بهره میبرد.
به علاوه توسط Take و Skip کار صفحه بندی و بازگشت تنها بازهای از اطلاعات مورد نیاز، انجام میشود.
ب) لیست جنریک محصولات، در نهایت باید با فرمت JqGridData به صورت JSON بازگشت داده شود. نحوهی این Projection را در اینجا میتوانید ملاحظه کنید.
هر ردیف این لیست، باید تبدیل شود به ردیفی از جنس JqGridRowData، تا توسط jqGrid قابل پردازش گردد.
- توسط مقدار دهی UserData، برچسبی را در ذیل ستون Name و مقداری را در ذیل ستون Price نمایش خواهیم داد.
برای مطالعهی بیشتر
بهترین راهنمای جزئیات این Grid، مستندات آنلاین آن هستند: http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs
همچنین این مستندات را با فرمت PDF نیز میتوانید مطالعه کنید: http://www.trirand.com/blog/jqgrid/downloads/jqgriddocs.pdf
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid01.zip
مثالهای سری jqGrid تغییرات زیادی داشتند. برای دریافت آنها به این مخزن کد مراجعه کنید.