public class LocalizedRequiredAttribute : RequiredAttribute, IClientValidatable { private readonly string _resourceKey; public string SourceName { get; set; } = LocalizationSourceNames.Default; public LocalizedRequiredAttribute(string resourceKey) { _resourceKey = resourceKey; } public override string FormatErrorMessage(string name) { return LocalizationHelper.GetString(SourceName, _resourceKey); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { yield return new ModelClientValidationRequiredRule(FormatErrorMessage(null)); } }
روی متدهای GetAllCategory و GetAllNews به صورت جداگانه کلیک کنید. متوجه خواهید شد که هرچند در کلاس tblNews شیای از نوع tblCategory و در کلاس tblCategory شیای از نوع مجموعهی tblNews به صورت Virtual تعریف شده است ولی در بر خلاف انتظارمان اثری از آن در اینجا دیده نمیشود. نتیجهی مشاهدهشده به خاطر است که در هر دو تعریف صفت DataMember را به ویژگیهای ناوبری اختصاص نداده ایم و این میتواند راهبرد ما در طراحی WCF باشد. ولی اگر میخواهید ویژگی ناوبری میان موجودیتها در متدهای ما هم دیده شود ادامهی این درس را بخوانید وگرنه ممکن است تصمیم داشته باشید در صورت نیاز به پیوند میان موجودیتها، متد جدیدی بنویسید و از دستورهای Linq استفاده کنید و یا برای اینکار از Stored Procedured بهره ببرید.
در اینجا من این سناریو را دنبال میکنم که در صورتی که متد GetAllNews اجرا شود؛ بدون اینکه نیاز باشد برای دانستن نام دستهی خبر از متد دیگری مانند GetAllCategory استفاده کنیم؛ رکورد وابسته موجودیت دسته در هر خبر نشان داده شود.
از Solution Explorer فایل MyNewsModel.tt را باز کنید و دنبال کد زیر بگردید:
public string NavigationProperty(NavigationProperty navigationProperty) { var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); return string.Format( CultureInfo.InvariantCulture, "{0} {1} {2} {{ {3}get; {4}set; }}", AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, _code.Escape(navigationProperty), _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); }
سپس آنرا به صورت زیر ویرایش کنید:
public string NavigationProperty(NavigationProperty navigationProperty) { var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); return string.Format( CultureInfo.InvariantCulture, "{0}{1} {2} {3} {{ {4}get; {5}set; }}", navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many ? "[DataMember]" + Environment.NewLine : "", AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, _code.Escape(navigationProperty), _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); }
پس از ذخیرهی فایل، خواهید دید که صفت DataMember در کلاس tblNews پیش از ویژگی tblCategory افزوده شده است. بار دیگر پروژه را اجرا کنید. روی متد GetAllNews کلیک کنید و روی دکمه Invoke بفشارید. خواهید دید که هرچند tblCategory در ویژگیهای آن قرار گرفته است ولی مقدار آن Null است. برای حل این مشکل باید از Solution Explorer فایل MyNewsService.cs را باز کنید و به به جای کد مربوط به متدهای GetAllNews و GetNews کدهای زیر را قرار دهید:
public List<tblNews> GetAllNews()
{
return dbMyNews.tblNews.Include(p=>p.tblCategory).Where(c=>c.IsDeleted == false).ToList();
}
public tblNews GetNews(int tblNewsId)
{
return dbMyNews.tblNews.Include(p => p.tblCategory).FirstOrDefault(p => p.tblNewsId == tblNewsId);
}
این بار اگر پروژه را اجرا کنید با نتیجهای مانند شکل زیر روبهرو خواهید شد:
در بخش هفتم پیرامون میزبانی WCF Library خواهم نوشت.
مجوز استفاده از فایلهای جاوا اسکریپتی آن 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 تغییرات زیادی داشتند. برای دریافت آنها به این مخزن کد مراجعه کنید.
var newIdentity = new ClaimsIdentity(context.Ticket.Identity); var roleID = form.Result["roleID"]; newIdentity.AddClaim(new Claim("roleID", roleID)); newIdentity.AddClaim(new Claim("newClaim", "refreshToken")); newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties); context.Validated(newTicket);
- Get/GetAsync(with data retriever)
- Get/GetAsync(without data retriever)
- Set/SetAsync
- Remove/RemoveAsync
- ~~Refresh/RefreshAsync (was removed)~~
- RemoveByPrefix/RemoveByPrefixAsync
- SetAll/SetAllAsync
- GetAll/GetAllAsync
- GetByPrefix/GetByPrefixAsync
- RemoveAll/RemoveAllAsync
- GetCount
- Flush/FlushAsync
- TrySet/TrySetAsync
- GetExpiration/GetExpirationAsync
- In-Memory
- Memcached
- Redis(Based on StackExchange.Redis)
- Redis(Based on csredis)
- SQLite
- Hybrid
- Disk
- LiteDb
- BinaryFormatter
- MessagePack
- Newtonsoft.Json
- Protobuf
- System.Text.Json
Install-Package EasyCaching.InMemory
services.AddEasyCaching(options => { // use memory cache with a simple way options.UseInMemory(); }
// تنظیم یک کش با کلید - مقدار - زمان انقضا void Set<T>(string cacheKey, T cacheValue, TimeSpan expiration); Task SetAsync<T>(string cacheKey, T cacheValue, TimeSpan expiration); // تنظیم یک کش با مقدار و زمان انقضا که تایپ مقدار از نوع دیکشنری هست و کلید دیکشنری بعنوان کلید کش قرار میگیرد void SetAll<T>(IDictionary<string, T> value, TimeSpan expiration); Task SetAllAsync<T>(IDictionary<string, T> value, TimeSpan expiration); // تنظیم یک کش با کلید - مقدار - زمان انقضا // اگر کلیدی همنام وجود داشته باشد مقدار نادرست و در غیر اینصورت مقدار نادرست را برمیگرداند bool TrySet<T>(string cacheKey, T cacheValue, TimeSpan expiration); Task<bool> TrySetAsync<T>(string cacheKey, T cacheValue, TimeSpan expiration); // گرفتن یک کش با کلید CacheValue<T> Get<T>(string cacheKey); Task<CacheValue<T>> GetAsync<T>(string cacheKey); // CacheValue<T> Get<T>(string cacheKey, Func<T> dataRetriever, TimeSpan expiration); Task<CacheValue<T>> GetAsync<T>(string cacheKey, Func<Task<T>> dataRetriever, TimeSpan expiration); // گرفتن یک کش با چند کاراکتر پیشین کلید آن // برای مثال یک کلید با نام // MyKey // تنها با داشتن چند حرف اول // MyK // میتوانیم این کش را دریافت کنیم IDictionary<string, CacheValue<T>> GetByPrefix<T>(string prefix); Task<IDictionary<string, CacheValue<T>>> GetByPrefixAsync<T>(string prefix); // IDictionary<string, CacheValue<T>> GetAll<T>(IEnumerable<string> cacheKeys); Task<IDictionary<string, CacheValue<T>>> GetAllAsync<T>(IEnumerable<string> cacheKeys); // گرفتن تعداد کشهای با کاراکترهای پیشین کلید که میان چند کلید یکسان است int GetCount(string prefix = ""); Task<int> GetCountAsync(string prefix = ""); // گرفتن زمان انقضا باقیمانده از یک کش با کلید آن TimeSpan GetExpiration(string cacheKey); Task<TimeSpan> GetExpirationAsync(string cacheKey); // حذف کردن یک کش با کلید void Remove(string cacheKey); Task RemoveAsync(string cacheKey); // حذف کردن یک کش با چند کاراکتر پیشین کلید void RemoveByPrefix(string prefix); Task RemoveByPrefixAsync(string prefix); // حذف کردن چند کش با لیستی از کلیدها void RemoveAll(IEnumerable<string> cacheKeys); Task RemoveAllAsync(IEnumerable<string> cacheKeys); // بررسی وجود یا عدم وجود یک کش با کلید bool Exists(string cacheKey); Task<bool> ExistsAsync(string cacheKey); // حذف کردن همه کشها void Flush(); Task FlushAsync();
Install-Package EasyCaching.Redis
services.AddEasyCaching(option => { option.UseRedis(config => { config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); }); });
- متدهای Keys
// حذف کردن یک کلید در صورت وجود bool KeyDel(string cacheKey); Task<bool> KeyDelAsync(string cacheKey); // تنظیم تاریخ انتضا به یک کلید موجود بر حسب ثانیه bool KeyExpire(string cacheKey, int second); Task<bool> KeyExpireAsync(string cacheKey, int second); // بررسی وجود یا عدم وجود یک کلید bool KeyExists(string cacheKey); Task<bool> KeyExistsAsync(string cacheKey); // گرفتن زمان انتقضا باقیمانده یک کلید long TTL(string cacheKey); Task<long> TTLAsync(string cacheKey); // جستجو بین همه کلیدها براساس فیلتر شامل بودن نام کلید از مقدار ورودی List<string> SearchKeys(string cacheKey, int? count = null);
- متدهای String
// افزودن یک عدد (پیشقرض 1) به مقدار نوع عددی یک کلید long IncrBy(string cacheKey, long value = 1); Task<long> IncrByAsync(string cacheKey, long value = 1); // افزودن یک عدد (پیشقرض 1) به مقدار نوع عددی یک کلید double IncrByFloat(string cacheKey, double value = 1); Task<double> IncrByFloatAsync(string cacheKey, double value = 1); // تنظیم یک کلید و مقدار وقتی مقدار از نوع رشته باشد bool StringSet(string cacheKey, string cacheValue, TimeSpan? expiration = null, string when = ""); Task<bool> StringSetAsync(string cacheKey, string cacheValue, TimeSpan? expiration = null, string when = ""); // گرفتن کلید و مقدار آن وقتی مقدار از نوع رشته باشد string StringGet(string cacheKey); Task<string> StringGetAsync(string cacheKey); // گرفتن تعداد کاراکترهای مقدار یک کلید وقتی مقدار از نوع رشته باشد long StringLen(string cacheKey); Task<long> StringLenAsync(string cacheKey); // جایگزاری یک رشته درون رشته مقدار یک کلید بعد از شماره کاراکتر مشخص شده در ورودی برای مثال // "Hello World" // 6 , jack // "Hello jack" long StringSetRange(string cacheKey, long offest, string value); Task<long> StringSetRangeAsync(string cacheKey, long offest, string value); // گرفتن یک بازه از رشته مقدار یک کلید با شماره کاراکتر شروع و پایان string StringGetRange(string cacheKey, long start, long end); Task<string> StringGetRangeAsync(string cacheKey, long start, long end);
- متدهای Hashes
// شما میتوانید دو کلید با نامهای یکسان داشته باشید که در کلید تایپ دیکشنری مقدار خود باهم متفاوت هستند bool HMSet(string cacheKey, Dictionary<string, string> vals, TimeSpan? expiration = null); Task<bool> HMSetAsync(string cacheKey, Dictionary<string, string> vals, TimeSpan? expiration = null); // شما میتوانید دو کلید با نامهای یکسان داشته باشید که در ورودی فیلد باهم متفاوت هستند bool HSet(string cacheKey, string field, string cacheValue); Task<bool> HSetAsync(string cacheKey, string field, string cacheValue); // بررسی وجود یا عدم وجود یک کلید و فیلد bool HExists(string cacheKey, string field); Task<bool> HExistsAsync(string cacheKey, string field); // حذف کردن کلیدهای همنام موجود با همه فیلدهای متفاوت در حالت پیشفرض مگر اینکه کلید و نام فیلد را بهمراه آن مشخص کنید long HDel(string cacheKey, IList<string> fields = null); Task<long> HDelAsync(string cacheKey, IList<string> fields = null); // گرفتن مقدار با نام کلید و نام فیلد string HGet(string cacheKey, string field); Task<string> HGetAsync(string cacheKey, string field); // گرفتن فیلد و مقدار با کلید Dictionary<string, string> HGetAll(string cacheKey); Task<Dictionary<string, string>> HGetAllAsync(string cacheKey); // افزودن یک عدد (پیشقرض 1) به مقدار نوع عددی یک کلید و فیلد long HIncrBy(string cacheKey, string field, long val = 1); Task<long> HIncrByAsync(string cacheKey, string field, long val = 1); // گرفتن فیلدهای متفاوت یک کلید List<string> HKeys(string cacheKey); Task<List<string>> HKeysAsync(string cacheKey); // گرفتن تعداد فیلدهای متفاوت یک کلید long HLen(string cacheKey); Task<long> HLenAsync(string cacheKey); // گرفتن مقادیر یک کلید بدون در نظر گرفتن فیلدهای متفاوت List<string> HVals(string cacheKey); Task<List<string>> HValsAsync(string cacheKey); // گرفتن مقدار دیکشنری با کلید و نام فیلدها Dictionary<string, string> HMGet(string cacheKey, IList<string> fields); Task<Dictionary<string, string>> HMGetAsync(string cacheKey, IList<string> fields);
- متدهای List
// گرفتن یک مقدار از لیست مقادیر با شماره ایندکس آن T LIndex<T>(string cacheKey, long index); Task<T> LIndexAsync<T>(string cacheKey, long index); // گرفتن تعداد مقادیر در لیست یک کلید long LLen(string cacheKey); Task<long> LLenAsync(string cacheKey); // گرفتن اولین مقدار از مقادیر یک لیست در یک کلید T LPop<T>(string cacheKey); Task<T> LPopAsync<T>(string cacheKey); // ایجاد یک کلید که لیستی از مقادیر را پشتیبانی میکند و میتوانید هر بار مقدار جدید به لیست آن اضافه کنید long LPush<T>(string cacheKey, IList<T> cacheValues); Task<long> LPushAsync<T>(string cacheKey, IList<T> cacheValues); // گرفتن مقادیر یک لیست از داده بر اساس شماره ایندکس شروع و پایان برای مثال مقادیر ۳ تا ۷ از ۱۰ مقدار List<T> LRange<T>(string cacheKey, long start, long stop); Task<List<T>> LRangeAsync<T>(string cacheKey, long start, long stop); // حذف کردن مقادیر یک لیست بر اساس تعداد وارد شده که بعد از مقدار وارد شده شروع به شمارش میشود long LRem<T>(string cacheKey, long count, T cacheValue); Task<long> LRemAsync<T>(string cacheKey, long count, T cacheValue); // افزودن یک مقدار به لیستی از مقادیر یک کلید با گرفتن شماره ایندکس bool LSet<T>(string cacheKey, long index, T cacheValue); Task<bool> LSetAsync<T>(string cacheKey, long index, T cacheValue); // بررسی میکند که لیست مقداری برای شماره ایندکس شروع و پایان درون خودش دارد یا خیر bool LTrim(string cacheKey, long start, long stop); Task<bool> LTrimAsync(string cacheKey, long start, long stop); // https://redis.io/commands/lpushx long LPushX<T>(string cacheKey, T cacheValue); Task<long> LPushXAsync<T>(string cacheKey, T cacheValue); // https://redis.io/commands/linsert long LInsertBefore<T>(string cacheKey, T pivot, T cacheValue); Task<long> LInsertBeforeAsync<T>(string cacheKey, T pivot, T cacheValue); // https://redis.io/commands/linsert long LInsertAfter<T>(string cacheKey, T pivot, T cacheValue); Task<long> LInsertAfterAsync<T>(string cacheKey, T pivot, T cacheValue); // https://redis.io/commands/rpushx long RPushX<T>(string cacheKey, T cacheValue); Task<long> RPushXAsync<T>(string cacheKey, T cacheValue); // https://redis.io/commands/rpush long RPush<T>(string cacheKey, IList<T> cacheValues); Task<long> RPushAsync<T>(string cacheKey, IList<T> cacheValues); // https://redis.io/commands/rpop T RPop<T>(string cacheKey); Task<T> RPopAsync<T>(string cacheKey);
- متدهای Set
// https://redis.io/commands/SAdd long SAdd<T>(string cacheKey, IList<T> cacheValues, TimeSpan? expiration = null); Task<long> SAddAsync<T>(string cacheKey, IList<T> cacheValues, TimeSpan? expiration = null); // https://redis.io/commands/SCard long SCard(string cacheKey); Task<long> SCardAsync(string cacheKey); // https://redis.io/commands/SIsMember bool SIsMember<T>(string cacheKey, T cacheValue); Task<bool> SIsMemberAsync<T>(string cacheKey, T cacheValue); // https://redis.io/commands/SMembers List<T> SMembers<T>(string cacheKey); Task<List<T>> SMembersAsync<T>(string cacheKey); // https://redis.io/commands/SPop T SPop<T>(string cacheKey); Task<T> SPopAsync<T>(string cacheKey); // https://redis.io/commands/SRandMember List<T> SRandMember<T>(string cacheKey, int count = 1); Task<List<T>> SRandMemberAsync<T>(string cacheKey, int count = 1); // https://redis.io/commands/SRem long SRem<T>(string cacheKey, IList<T> cacheValues = null); Task<long> SRemAsync<T>(string cacheKey, IList<T> cacheValues = null);
- متدهای Stored Set
// https://redis.io/commands/ZAdd long ZAdd<T>(string cacheKey, Dictionary<T, double> cacheValues); Task<long> ZAddAsync<T>(string cacheKey, Dictionary<T, double> cacheValues); // https://redis.io/commands/ZCard long ZCard(string cacheKey); Task<long> ZCardAsync(string cacheKey); // https://redis.io/commands/ZCount long ZCount(string cacheKey, double min, double max); Task<long> ZCountAsync(string cacheKey, double min, double max); // https://redis.io/commands/ZIncrBy double ZIncrBy(string cacheKey, string field, double val = 1); Task<double> ZIncrByAsync(string cacheKey, string field, double val = 1); // https://redis.io/commands/ZLexCount long ZLexCount(string cacheKey, string min, string max); Task<long> ZLexCountAsync(string cacheKey, string min, string max); // https://redis.io/commands/ZRange List<T> ZRange<T>(string cacheKey, long start, long stop); Task<List<T>> ZRangeAsync<T>(string cacheKey, long start, long stop); // https://redis.io/commands/ZRank long? ZRank<T>(string cacheKey, T cacheValue); Task<long?> ZRankAsync<T>(string cacheKey, T cacheValue); // https://redis.io/commands/ZRem long ZRem<T>(string cacheKey, IList<T> cacheValues); Task<long> ZRemAsync<T>(string cacheKey, IList<T> cacheValues); // https://redis.io/commands/ZScore double? ZScore<T>(string cacheKey, T cacheValue); Task<double?> ZScoreAsync<T>(string cacheKey, T cacheValue);
- متدهای Hyperloglog
// https://redis.io/commands/PfAdd bool PfAdd<T>(string cacheKey, List<T> values); Task<bool> PfAddAsync<T>(string cacheKey, List<T> values); // https://redis.io/commands/PfCount long PfCount(List<string> cacheKeys); Task<long> PfCountAsync(List<string> cacheKeys); // https://redis.io/commands/PfMerge bool PfMerge(string destKey, List<string> sourceKeys); Task<bool> PfMergeAsync(string destKey, List<string> sourceKeys);
- متدهای Geo
// https://redis.io/commands/GeoAdd long GeoAdd(string cacheKey, List<(double longitude, double latitude, string member)> values); Task<long> GeoAddAsync(string cacheKey, List<(double longitude, double latitude, string member)> values); // https://redis.io/commands/GeoDist double? GeoDist(string cacheKey, string member1, string member2, string unit = "m"); Task<double?> GeoDistAsync(string cacheKey, string member1, string member2, string unit = "m"); // https://redis.io/commands/GeoHash List<string> GeoHash(string cacheKey, List<string> members); Task<List<string>> GeoHashAsync(string cacheKey, List<string> members); // https://redis.io/commands/GeoPos List<(decimal longitude, decimal latitude)?> GeoPos(string cacheKey, List<string> members); Task<List<(decimal longitude, decimal latitude)?>> GeoPosAsync(string cacheKey, List<string> members);
Install-Package EasyCaching.HybridCache Install-Package EasyCaching.InMemory Install-Package EasyCaching.Redis Install-Package EasyCaching.Bus.Redis
services.AddEasyCaching(option => // local option.UseInMemory("c1"); // distributed option.UseRedis(config => config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); }, "c2"); // combine local and distributed option.UseHybrid(config => // specify the local cache provider name after v0.5.4 config.LocalCacheProviderName = "c1" // specify the distributed cache provider name after v0.5.4 config.DistributedCacheProviderName = "c2" }); // use redis bus .WithRedisBus(busConf => busConf.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); }); });
- خواص تعریف شده در کلاسهای تنظیمات که قرار است به مقادیر و مداخل فایل appsetting.json نگاشت شوند، نیاز است get و set دار باشند.
public class MySettings { public string StringSetting { get; set; } public int IntSetting { get; set; }
- اما در مورد خواص پیچیده، اینطور نیست:
public List<string> ListValues { get; } = new List<string>();
- در حین تعریف خواص پیچیده از اینترفیسها و یا کلاسهای abstarct نمیتوان استفاده کرد:
public ISet<string> Values { get; set; }
public ISet<string> Values { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
البته باید دقت داشت که آغاز کنندهی تعریف شده باید دارای متد Add باشد (مانند IList و List)؛ در غیر اینصورت اطلاعاتی به این لیست اضافه نخواهد شد. برای نمونه نمیتوان از ReadOnlyCollection در اینجا استفاده کرد؛ چون متد Add ندارد.
columns: [ { field: "IsAvailable", title: "موجود است", template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>' } ]
همانطور که در این مثال نیز مشاهده میکنید، قالبهای Kendo UI از Hash (#) syntax استفاده میکنند. در اینجا قسمتهایی از قالب که با علامت # محصور میشوند، در حین اجرا، با اطلاعات فراهم شده جایگزین خواهند شد.
برای رندر مقادیر ساده میتوان از # =# استفاده کرد. از # :# برای رندر اطلاعات HTML-encoded کمک گرفته میشود و # # برای رندر کدهای جاوا اسکریپتی کاربرد دارد. از حالت HTML-encoded برای نمایش امن اطلاعات دریافتی از کاربران و جلوگیری از حملات XSS استفاده میشود.
اگر در این بین نیاز است # به صورت معمولی رندر شود، در حالت کدهای جاوا اسکریپتی به صورت #\\ و در HTML ساده به صورت #\ باید مشخص گردد.
مثالی از نحوهی تعریف یک قالب Kendo UI
<!--دریافت اطلاعات از منبع محلی--> <script id="javascriptTemplate" type="text/x-kendo-template"> <ul> # for (var i = 0; i < data.length; i++) { # <li>#= data[i] #</li> # } # </ul> </script> <div id="container1"></div> <script type="text/javascript"> $(function () { var data = ['User 1', 'User 2', 'User 3']; var template = kendo.template($("#javascriptTemplate").html()); var result = template(data); //Execute the template $("#container1").html(result); //Append the result }); </script>
در ادامه باید این قالب را رندر کرد. برای این منظور یک div با id مساوی container1 را جهت تعیین محل رندر نهایی اطلاعات مشخص میکنیم. سپس متد kendo.template بر اساس id قالب اسکریپتی تعریف شده، یک شیء قالب را تهیه کرده و سپس با ارسال آرایهای به آن، سبب اجرای آن میشود. خروجی نهایی، یک قطعه کد HTML است که در محل container1 درج خواهد شد.
همانطور که ملاحظه میکنید، متد kendo.template، نهایتا یک رشته را دریافت میکند. بنابراین همینجا و به صورت inline نیز میتوان یک قالب را تعریف کرد.
کار با منابع داده راه دور
فرض کنید مدل برنامه به صورت ذیل تعریف شدهاست:
namespace KendoUI04.Models { public class Product { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsAvailable { set; get; } } }
using System.Collections.Generic; using System.Linq; using System.Web.Http; using KendoUI04.Models; namespace KendoUI04.Controllers { public class ProductsController : ApiController { public IEnumerable<Product> Get() { return ProductDataSource.LatestProducts.Take(10); } } }
<!--دریافت اطلاعات از سرور--> <div> <div id="container2"><ul></ul></div> </div> <script id="template1" type="text/x-kendo-template"> <li> #=Id# - #:Name# - #=kendo.toString(Price, "c")#</li> </script> <script type="text/javascript"> $(function () { var producatsTemplate1 = kendo.template($("#template1").html()); var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, error: function (e) { alert(e.errorThrown); }, change: function () { $("#container2 > ul").html(kendo.render(producatsTemplate1, this.view())); } }); productsDataSource.read(); }); </script>
هرچند خروجی دریافتی از سرور نهایتا یک آرایه از اشیاء Product است، اما در template1 اثری از حلقهی جاوا اسکریپتی مشاهده نمیشود. در اینجا چون از متد kendo.render استفاده میشود، نیازی به ذکر حلقه نیست و به صورت خودکار، به تعداد عناصر آرایه دریافتی از سرور، قطعه HTML قالب را تکرار میکند.
در ادامه برای کار با سرور از یک Kendo UI DataSource استفاده شدهاست. قسمت transport/read آن، کار تعریف محل دریافت اطلاعات را از سرور مشخص میکند. رویدادگران change آن اطلاعات نهایی دریافتی را توسط متد view در اختیار متد kendo.render قرار میدهد. در نهایت، قطعهی HTML رندر شدهی نهایی حاصل از اجرای قالب، در بین تگهای ul مربوط به container2 درج خواهد شد.
رویدادگران change زمانیکه data source، از اطلاعات راه دور و یا یک آرایهی جاوا اسکریپتی پر میشود، فراخوانی خواهد شد. همچنین مباحث مرتب سازی اطلاعات، صفحه بندی و تغییر صفحه، افزودن، ویرایش و یا حذف اطلاعات نیز سبب فراخوانی آن میگردند. متد view ایی که در این مثال فراخوانی شد، صرفا در روال رویدادگردان change دارای اعتبار است و آخرین تغییرات اطلاعات و آیتمهای موجود در data source را باز میگرداند.
یک نکتهی تکمیلی: فعال سازی intellisense کدهای جاوا اسکریپتی Kendo UI
اگر به پوشهی اصلی مجموعهی Kendo UI مراجعه کنید، یکی از آنها vsdoc نام دارد که داخل آن فایلهای min.intellisense.js و vsdoc.js مشهود هستند.
اگر از ویژوال استودیوهای قبل از 2012 استفاده میکنید، نیاز است فایلهای vsdoc.js متناظری را به پروژه اضافه نمائید؛ دقیقا در کنار فایلهای اصلی js موجود. اگر از ویژوال استودیوی 2012 و یا بالاتر استفاده میکنید باید از فایلهای intellisense.js متناظر استفاده کنید. برای مثال اگر از kendo.all.min.js کمک میگیرید، فایل متناظر با آن kendo.all.min.intellisense.js خواهد بود.
بعد از اینکار نیاز است فایلی به نام references.js_ را به پوشهی اسکریپتهای خود با این محتوا اضافه کنید (برای VS 2012 به بعد):
/// <reference path="jquery.min.js" /> /// <reference path="kendo.all.min.js" />
Tools menu –> Options -> Text Editor –> JavaScript –> Intellisense –> References
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
KendoUI04.zip
//قرار دهید containerBuilder.RegisterRequiredServices(); این دو را بعد از containerBuilder.RegisterHttpClient(); containerBuilder.RegisterIdentityClient();
توضیحات این کد در ادامه آمده است.
public virtual HttpClient HttpClient { get; set; } async Task CallUsersListApiUsingHttpClient() { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://reqres.in/api/users"); // Use request.Headers to set jwt token, ... // Use request.Content to send body. You can use StringContent, StreamContent, etc. HttpResponseMessage response = await HttpClient.SendAsync(request); response.EnsureSuccessStatusCode(); using (StreamReader streamReader = new StreamReader(await response.Content.ReadAsStreamAsync())) using (JsonReader jsonReader = new JsonTextReader(streamReader)) { List<UserDto> users = (await JToken.LoadAsync(jsonReader))["data"].ToObject<List<UserDto>>(); } }
{ "page": 2, "per_page": 3, "total": 12, "total_pages": 4, "data": [ { "id": 4, "first_name": "Eve", "last_name": "Holt", "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg" }, { "id": 5, "first_name": "Charles", "last_name": "Morris", "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg" } ] }
public class UserDto { [JsonProperty("id")] public int Id { get; set; } [JsonProperty("first_name")] public string FirstName { get; set; } [JsonProperty("last_name")] public string LastName { get; set; } [JsonProperty("avatar")] public string Avatar { get; set; } }
فرض کنید تنظیمات برنامه چنین شکلی را دارند:
{ "IdentityOptions": { "Lockout": { "MaxFailedAccessAttempts": 10, "DefaultLockoutTimeSpan": "0.00:05:00.0000" } } }
public class SiteSettings { public Identityoptions IdentityOptions { get; set; } } public class Identityoptions { public LockoutOptions Lockout { get; set; } }
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; }); services.Configure<SiteSettings>(options => Configuration.Bind(options)); var provider = services.BuildServiceProvider(); var siteSettingsOptions = provider.GetService<IOptions<SiteSettings>>(); // now use siteSettingsOptions.Value
آشنایی و بررسی ابزار Glimpse
بعد از آپلود سایت ما میتوانیم دسترسی به تنظیمات خاص glimpse را تنها به کاربران عضو محدود کنیم:
<location path="Glimpse.axd" > <system.web> <authorization> <allow users="Administrator" /> <deny users="*" /> </authorization> </system.web> </location>
یا میتوانیم آنرا غیرفعال کنیم :
<glimpse defaultRuntimePolicy="Off" xdt:Transform="SetAttributes"> </glimpse>
همچنین میتوانیم با پیاده سازی اینترفیس IRuntimePolicy سیاستهای مختلف نمایش تبهای glimpse را تعیین کنیم :
using Glimpse.AspNet.Extensions; using Glimpse.Core.Extensibility; namespace Test { public class GlimpseSecurityPolicy:IRuntimePolicy { public RuntimePolicy Execute(IRuntimePolicyContext policyContext) { // You can perform a check like the one below to control Glimpse's permissions within your application. // More information about RuntimePolicies can be found at http://getglimpse.com/Help/Custom-Runtime-Policy var httpContext = policyContext.GetHttpContext(); if (!httpContext.User.IsInRole("Administrator ")) { return RuntimePolicy.Off; } return RuntimePolicy.On; } public RuntimeEvent ExecuteOn { get { return RuntimeEvent.EndRequest; } } } }
زمانیکه glimpse را از طریق Nuget نصب میکنید کلاس فوق به صورت اتوماتیک به پروژه اضافه میشود با این تفاوت که به صورت کامنت شده است تنها کاری شما باید انجام بدید کدهای فوق را از حالت کامنت خارج کنید و Role مربوطه را جایگزین کنید.
نکته : کلاس فوق نیاز به رجیستر شدن ندارد و تشخیص آن توسط Glimpse به صورت خودکار انجام میشود.