cell.RunDirection = PdfWriter.RUN_DIRECTION_RTL; cell.RunDirection = PdfWriter.DirectionR2L;
public class ProductService { public IEnumerable<Product> GetOrderedProducts() { using (var ctx = new Entites()) { return ctx.Products.OrderBy(x => x.Name).ToList(); } } }
public interface IUnitOfWork { IDbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveAllChanges(); } public class Entites : DbContext, IUnitOfWork { public virtual DbSet<Product> Products { get; set; } // This is virtual because Moq needs to override the behaviour public new virtual IDbSet<TEntity> Set<TEntity>() where TEntity : class // This is virtual because Moq needs to override the behaviour { return base.Set<TEntity>(); } public int SaveAllChanges() { return base.SaveChanges(); } }
public class ProductService { private readonly IDbSet<Product> _products; private readonly IUnitOfWork _uow; public ProductService(IUnitOfWork uow) { _uow = uow; _products = _uow.Set<Product>(); } public IEnumerable<Product> GetOrderedProducts() { return _products.OrderBy(x => x.Name).ToList(); } }
[TestFixture] public class ProductServiceTest { private readonly ProductService _productService; public ProductServiceTest() { IQueryable<Product> data = GetRoadNetworks().AsQueryable(); var mockSet = new Mock<DbSet<Product>>(); mockSet.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(data.Provider); mockSet.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(data.Expression); mockSet.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(data.ElementType); mockSet.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); var context = new Mock<Entites>(); context.Setup(c => c.Products).Returns(mockSet.Object); context.Setup(m => m.Set<Product>()).Returns(mockSet.Object); _productService = new ProductService(context.Object); } private IEnumerable<Product> GetRoadNetworks() { return new List<Product> { new Product { Id = 1, Name = "A" }, new Product { Id = 2, Name = "B" }, new Product { Id = 3, Name = "C" } }; } [Test] public void GetOrderedProductTest() { IEnumerable<Product> products = _productService.GetOrderedProducts(); List<string> names = products.Select(x => x.Name).ToList(); var expected = new List<string> {"A", "B", "C"}; CollectionAssert.AreEqual(names, expected); } }
مجوز استفاده از فایلهای جاوا اسکریپتی آن 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 تغییرات زیادی داشتند. برای دریافت آنها به این مخزن کد مراجعه کنید.
• Next : یک عدد تصادفی را برای ما تولید میکند.
• NextByte : آرایهای از بایتها را که با اعداد تصادفی پر شدهاند تولید میکند.
• NextDouble : یک عدد تصادفی را بین 0.0 و 1.0 باز میگرداند.
بررسی متد Next
متد Next سه Overload مختلف دارد و این امکان را برای شما مهیا میکند تا 2 عدد را به عنوان بازه تولید اعداد تصادفی انتخاب کنید (حد پایین و بالای بازه).
تولید یک عدد تصادفی:
var rand = new Random().Next();
var rand = new Random().Next(1000);
کد زیر عددی تصادفی را بین محدوده تعیین شده تولید میکند:
public static int RandomNumber(int min, int max) { var rand = new Random(); return rand.Next(min, max); }
بررسی متد NextDouble
قطعه کد زیر (به کمک توابع کلاس Random) رشتهای تصادفی را با طول مشخصی برای ما تولید میکند؛ با قابلیت تعیین بزرگ و یا کوچک بودن کاراکترهای رشته تصادفی:
public static string RandomString(int size, bool lowerCase) { StringBuilder builder = new StringBuilder(); Random random = new Random(); char ch; for (int i = 0; i < size; i++) { //تولید عدد و تبدیل آن به کاراکتر ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); builder.Append(ch); } if (lowerCase) return builder.ToString().ToLower(); return builder.ToString(); }
public string RandomPassword() { StringBuilder builder = new StringBuilder(); builder.Append(RandomString(4, true)); builder.Append(RandomNumber(1000, 9999)); builder.Append(RandomString(2, false)); return builder.ToString(); }
همانطور که در ابتدای مطلب اشاره شد، خروجی این تابع آرایهای از بایتها میباشد و هر خانهی آن عددی است تصادفی که بزرگتر و یا مساوی 0 و کوچکتر از مقدار Maximum نوع داده Byte است. کد زیر نحوه استفاده از این تابع را نشان میدهد:
byte[] b = new byte[10]; Random rnd = new Random(); rnd.NextBytes(b); for (int i = 0; i < b.Length; i++) { Console.WriteLine(b[i]); }
153 115 86 5 161 190 249 228
using System; using System.Diagnostics; using System.IO; using System.Management; using Microsoft.Win32; namespace PdfFilePrinter { /// <summary> /// Executes the Adobe Reader and prints a file while suppressing the Acrobat print /// dialog box, then terminating the Reader. /// </summary> public class AcroPrint { /// <summary> /// The Adobe Reader or Adobe Acrobat path such as 'C:\Program Files\Adobe\Adobe Reader X\AcroRd32.exe'. /// If it's not specified, the InstalledAdobeReaderPath property value will be used. /// </summary> public string AdobeReaderPath { set; get; } /// <summary> /// Returns the default printer name. /// </summary> public string DefaultPrinterName { get { var query = new ObjectQuery("SELECT * FROM Win32_Printer"); using (var searcher = new ManagementObjectSearcher(query)) { foreach (var mo in searcher.Get()) { if (((bool?)mo["Default"]) ?? false) return mo["Name"] as string; } } return string.Empty; } } /// <summary> /// The name and path of the PDF file to print. /// </summary> public string PdfFilePath { set; get; } /// <summary> /// Name of the printer such as '\\PrintServer\HP LaserJet'. /// If it's not specified, the DefaultPrinterName property value will be used. /// </summary> public string PrinterName { set; get; } /// <summary> /// Returns the HKEY_CLASSES_ROOT\Software\Adobe\Acrobat\Exe value. /// If AcroRd32.exe does not exist, returns string.Empty /// </summary> public string InstalledAdobeReaderPath { get { var acroRd32Exe = Registry.ClassesRoot.OpenSubKey(@"Software\Adobe\Acrobat\Exe", writable: false); if (acroRd32Exe == null) return string.Empty; var exePath = acroRd32Exe.GetValue(string.Empty) as string; if (string.IsNullOrEmpty(exePath)) return string.Empty; exePath = exePath.Trim(new[] { '"' }); return File.Exists(exePath) ? exePath : string.Empty; } } /// <summary> /// Executes the Adobe Reader and prints a file while suppressing the Acrobat print /// dialog box, then terminating the Reader. /// </summary> /// <param name="timeout">The amount of time, in milliseconds, to wait for the associated process to exit. The maximum is the largest possible value of a 32-bit integer, which represents infinity to the operating system.</param> public void PrintPdfFile(int timeout = Int32.MaxValue) { if (!File.Exists(PdfFilePath)) throw new ArgumentException(PdfFilePath + " does not exist."); var args = string.Format("/N /T \"{0}\" \"{1}\"", PdfFilePath, getPrinterName()); var process = startAdobeProcess(args); if (!process.WaitForExit(timeout)) process.Kill(); } private Process startAdobeProcess(string arguments = "") { var startInfo = new ProcessStartInfo { FileName = this.getExePath(), Arguments = arguments, CreateNoWindow = true, ErrorDialog = false, UseShellExecute = false, Verb = "print" }; return Process.Start(startInfo); } private string getPrinterName() { var printer = PrinterName; if (string.IsNullOrEmpty(printer)) printer = DefaultPrinterName; if (string.IsNullOrEmpty(printer)) throw new ArgumentException("Please set the PrinterName."); return printer; } private string getExePath() { var exePath = AdobeReaderPath; if (string.IsNullOrEmpty(exePath) || !File.Exists(exePath)) exePath = InstalledAdobeReaderPath; if (string.IsNullOrEmpty(exePath)) throw new ArgumentException("Please set the full path of the AcroRd32.exe or Acrobat.exe."); return exePath; } } }
توضیحات:
استفاده ابتدایی از کلاس فوق به نحو زیر است:
new AcroPrint { PdfFilePath = @"D:\path\test.pdf" }.PrintPdfFile();
ملاحظات:
- کدهای فوق نیاز به ارجاعی به اسمبلی استاندارد System.Management.dll نیز دارند.
- اگر علاقمند بودید که چاپگر خاصی را معرفی کنید (برای مثال یک چاپگر تعریف شده در شبکه)، میتوانید خاصیت PrinterName را مقدار دهی نمائید.
- محل نصب Adobe reader از رجیستری ویندوز استخراج میشود. اما اگر محل نصب برنامه استاندارد نبود، نیاز است خاصیت AdobeReaderPath مقدار دهی گردد.
- تحت هر شرایطی برنامه Adobe reader ظاهر خواهد شد؛ حتی اگر در حین آغاز پروسه سعی در مخفی کردن پنجره آن نمائید. اینکار به عمد جهت مسایل امنیتی در این برنامه درنظر گرفته شده است تا کاربر بداند که پروسه چاپ آغاز شده است.
انواع ارجاعی
قبلا در مورد مقادیر ارجاعی صحبت کردیم. در اینجا نیز به این موضوع اشاره میکنیم که هر مقدار ارجاعی، نمونهای ایجاد شده از یک نوع ارجاعی میباشد. انواع ارجاعی در واقع ساختارهایی هستند که جهت گروه بندی دادهها و عملکرد بین آنها استفاده میشوند. در سایر زبانهای برنامه نویسی شیء گرا، به انواع ارجاعی، کلاس و به مقادیر ارجاعی، شیء میگویند. در جاوا اسکریپت نیز، به مقادیر ارجاعی و یا نمونههای ایجاد شده از انواع ارجاعی، شیء میگویند. به انواع ارجاعی، توصیف گر شیء نیز میگویند؛ زیرا ویژگیها و متدهای آن شیء را معرفی و توصیف مینماید.
نحوهی ایجاد شیء از نوع ارجاعی Object
از آنجاییکه نوع ارجاعی Object هیچ ویژگی و متد خاصی ندارد، متداولترین نوع ارجاعی جهت ایجاد اشیاء سفارشی میباشد. به دو روش میتوان نمونهای را از یک Object ایجاد نمود. روش اول استفاده از عملگر new و بصورت زیر میباشد:
var person = new Object (); person . name = "Meysam" ; person . age = 32 ;
با استفاده از عملگر new، شیء person را
از نوع Object
ایجاد نمودیم که شامل دو ویژگی (Property) به
نامهای name و age میباشد. در واقع شیء person
ساختاری را جهت نگهداری اطلاعات یک شخص معرفی میکند. این عمل موجب جلوگیری از
پراکندگی تعریف متغیرها و گروه بندی آنها در قالب یک شیء میشود. روش دوم استفاده
از Object Literal Notation یا
نماد تحت الفظی شیء و بصورت زیر میباشد:
var person = { name : "Meysam" , age : 32 };
Object Literal Notation ،
دستور میانبری برای ایجاد یک شیء از نوع Object میباشد. در مثال فوق هم، همانند
روش اول، شیء person را
با دو ویژگی name و age ایجاد
نمودهایم. در این روش، نام ویژگیها میتوانند به
صورت رشتهای و عددی نیز به یک شیء اختصاص یابد.
var person = { "name" : "Meysam" , "age" : 32 };
معمولا از دو روش فوق زمانی استفاده میشود که میخواهیم اشیایی را ایجاد نماییم که ویژگیهای آنها فقط خواندنی باشند. با استفاده
از روش دوم، حتی میتوان یک شیء خالی را ایجاد نمود که در ابتدا هیچ ویژگی ای
ندارد و در مراحل بعد، ویژگیهایی را به آن
اضافه نمود.
var person = {}; // var person = new Object(); person . name = "Meysam" ; person . age = 32 ;
در مثال فوق شیء person بدون ویژگیها تعریف شده است؛ سپس به آن ویژگیهایی را اضافه نمودهایم.
استفاده از روش Object Literal Notation ، یکی از روشهای محبوب برنامه نویسان جاوا اسکریپت میباشد. زیرا با کمترین کد و بصورت بصری، شیء ای را ایجاد نموده و مثلا به یک متد ارسال مینمایند. به مثال زیر توجه کنید:
function displayInfo ( arg ) { var output = "" ; if ( arg . name != undefined ) output += "Name: " + arg . name + "\n" ; if ( arg . age != undefined ) output += "Age: " + arg . age + "\n" ; return output ; } alert (displayInfo ({ name : "Meysam" , age : 32 })); alert (displayInfo ({ name : "Sohrab" }));
در این مثال یک تابع تعریف شده است که آرگومان
ورودی آن یک شیء میباشد. در تابع بررسی میشود که اگر ویژگی name و یا age
برای این آرگومان تعریف شده بود، خروجی تابع را تولید نماید. در واقع این ویژگیها اختیاری میباشند و میتوانند ارسال نگردند. در زمان فراخوانی تابع نیز شیء ای را
بصورت Object Literal Notation
ایجاد نموده و به تابع ارسال کردیم.
این الگو برای ارسال آرگومان، زمانی استفاده میشود که تعداد زیادی آرگومان اختیاری داریم و میخواهیم به تابع ارسال نماییم. معمولا کار با آرگومانهای نامی (Named Arguments) راحتتر است ولی زمانیکه تعداد آرگومانهای اختیاری زیاد باشند، مدیریت و نگهداری آنها سخت و طاقت فرسا میگردد و ظاهر زشتی را به تابع میدهد. بهترین راهکار جهت مدیریت چنین شرایطی این است که آرگومانهای اجباری را به صورت آرگومانهای نامی تعریف کنیم و آرگومانهای اختیاری را به صورت یک شیء به تابع ارسال کنیم.
نکتهی دیگری که باید به آن توجه نمود این است که جهت دسترسی به ویژگیهای یک شیء از (.) استفاده میشود. همچنین میتوان به یک ویژگی با استفاده از [] و بصورت یک آرایه دسترسی داشت که در این صورت نام ویژگی بصورت رشتهای در [] ذکر خواهد شد.
alert ( person . name ); alert ( person [ "name" ]);
در عمل تفاوتی بین دو مورد فوق وجود ندارد. مهمترین
مزیت استفاده از [] این است که میتوانید توسط یک متغیر به ویژگیهای یک شیء دسترسی
داشته باشید. همچنین اگر نام ویژگی شامل کاراکترهایی باشد که خطای گرامری رخ میدهد یا از اسامی رزرو شده استفاده کرده باشید، میتوانید از [] جهت دسترسی به
ویژگی استفاده نمایید.
var propertyName = "name" ; alert ( person [ propertyName ]); alert ( person [ "first name" ]);
در دستور alert اول، با استفاده از یک متغیر به ویژگی name از شیء person دسترسی پیدا نمودیم. در دستور آخر نیز، به دلیل وجود space در نام ویژگی، مجبور هستیم جهت دسترسی به ویژگی first name از [] استفاده نماییم.
بررسی نوع ارجاعی Function
توابع در واقع یک شیء و نمونهای از نوع ارجاعی Function میباشند که میتوانند همانند سایر اشیاء ویژگیها و متدهای خاص خود را داشته باشند. بنابراین میتوان در یک عبارت، تابعی را به یک شیء نسبت داد. به مثال زیر توجه کنید:
var sum = function ( a , b ) { return a + b ; }; alert ( sum ( 10 , 20 ));
خروجی :
30
شیء sum را تعریف نموده و یک تابع را به آن اختصاص دادیم که شامل دو آرگومان ورودی میباشد. حال میتوان با شیء sum همانند یک تابع رفتار نمود و تابع مورد نظر را فراخوانی کرد. توجه داشته باشید که هیچ نامی را در زمان تعریف تابع به آن اختصاص ندادهایم. به این شکل تعریف تابع، Function Expression میگویند.
همانند سایر اشیاء، نام تابع نیز اشارهگری به تابع میباشد. بنابراین میتوان توابع را نیز به یکدیگر نسبت داد. به مثال زیر توجه کنید:
function sum ( a , b ) { return a + b ; } alert ( sum ( 10 , 10 )); var anotherSum = sum ; alert ( anotherSum ( 10 , 10 )); sum = null ; alert ( anotherSum ( 10 , 10 )); alert ( sum ( 10 , 10 )); // Error: Object is not a function;
خروجی :
20
20
20
Error: Object is not a function
ابتدا تابعی را به نام sum ایجاد نمودیم که دو عدد را با هم جمع میکند. دقت داشته باشید که به این شکل تعریف تابع sum ، اعلان تابع یا Function Declaration میگویند. سپس متغیری را به نام anotherSum ، تعریف نموده و sum را به آن نسبت دادیم. توجه داشته باشید که در زمان انتساب یک تابع به یک متغیر نباید () را ذکر کنیم، زیرا ذکر () به منزلهی فراخوانی تابع و اختصاص خروجی آن به متغیر میباشد و نه انتساب اشاره گر تابع به آن متغیر. فراخوانی sum و anotherSum خروجی یکسانی را دارند؛ زیرا هر دو به یک تابع اشاره میکنند. در خطوط بعدی، شیء sum را با مقدار null تنظیم نمودیم. در واقع با این کار اشارهگر sum برابر null شده است؛ یعنی دیگر به هیچ تابعی اشاره نمیکند. ولی تابع همچنان در حافظه وجود دارد؛ زیرا اشارهگر دیگری به نام anotherSum در حال اشاره نمودن به آن میباشد. در مرحلهی بعدی که sum را فراخوانی نمودیم، به دلیل null بودن آن، خطا رخ خواهد داد.
بازنگری مجدد به مبحث Overloading
در اینجا فقط میخواهیم اشارهای کنیم به مبحث Overloading که قبلا در مورد آن بحث کردیم تا دلیل فنی عدم وجود Overloading را در جاوا اسکریپت درک کنیم. همانطور که قبلا بیان شد، نام تابع در واقع اشاره گری به تابع میباشد؛ بنابراین تعریف دو تابع هم نام، همانند اختصاص مجدد مقدار یا تغییر مقدار یک متغیر میباشد. به مثال زیر توجه کنید:
function calc ( num1 , num2 ) { return num1 + num2 ; } function calc ( num1 , num2 ) { return num1 - num2 ; }
همانطور که پیشتر نیز عنوان شد، تابع دوم جایگزین تابع اول میگردد. در واقع تعریف فوق همانند تعریف زیر میباشد:
var calc = function ( num1 , num2 ) { return num1 + num2 ; }; calc = function ( num1 , num2 ) { return num1 - num2 ; };
همانطور که میبینید، ابتدا متغیری به نام calc تعریف شدهاست و با یک تابع مقداردهی اولیه شده است. سپس با تابع دوم مقداردهی مجدد گردیده است و دیگر به تابع قبلی اشاره نمیکند. بنابراین همیشه تابع آخر جایگزین توابع قبلی میگردد.
Function Declaration در مقابل Function Expression
این دو روش تعریف و استفاده از توابع تقریبا مشابه هم میباشند و فقط یک تفاوت اصلی بین آنها وجود دارد و آن به نحوهی رفتار موتور پردازشی جاوا اسکریپت بر میگردد. Function Declaration قبل از اینکه کدهای جاوا اسکریپت خوانده و اجرا شوند، خوانده شده و در دسترس خواهند بود؛ اما Function Expression تا زمانی که روال اجرای کد به این خط از کد نرسد، اجرا نخواهد شد و در دسترس نخواهد بود. به مثال Function Declaration زیر توجه کنید:
alert ( sum ( 10 , 20 )); function sum ( a , b ) { return a + b ; }
خروجی :
30
قبل از اعلان تابع sum ، این تابع فراخوانی شده است؛ ولی هیچ خطایی رخ نمیدهد. زیرا قبل از اجرای دستورات، تابع sum خوانده و در دسترس خواهد بود. اما اگر تابع فوق بصورت Function Expression تعریف شده بود، خطا رخ میداد و برنامه اجرا نمیشد.
alert ( sum ( 10 , 20 )); // Error: undefined is not a function var sum = function ( a , b ) { return a + b ; };
خروجی :
Error: undefined is not a function
همانطور که میبینید، در خط اول برنامه، خطای اجرایی رخ داده است. زیرا هنوز روال اجرایی برنامه به خط تعریف تابع sum نرسیدهاست. بنابراین تابع sum در دسترس نخواهد بود و فراخوانی آن موجب بروز خطا میگردد.
ارسال تابع به عنوان ورودی یا خروجی توابع دیگر
همانطور که قبلا بیان شد، نام تابع در واقع یک متغیر میباشد که به تابع مورد نظر اشاره میکند. بنابراین میتوان همانند یک متغیر با آن رفتار نموده و به عنوان آرگومان ورودی و یا مقدار خروجی یک تابع آن را ارسال نمود. به مثال زیر توجه کنید:
function add ( a , b ) { return a + b ; } function mult ( a , b ) { return a * b ; } function calc ( a , b , fn ) { return a * b + fn ( a , b ); } alert ( calc ( 10 , 20 , add )); alert ( calc ( 10 , 20 , mult ));
خروجی :
230
400
دو تابع به نامهای add و mult با دو آرگومان ورودی تعریف شدهاند که به ترتیب حاصل جمع و حاصل ضرب دو آرگومان را بر میگردانند. تابع calc نیز با 3 آرگومان ورودی تعریف شدهاست که قصد داریم برای آرگومان سوم یک تابع را ارسال نماییم. تابع calc در 2 مرحله فراخوانی شدهاست که در یک مرحله تابع add و در مرحلهی دیگر تابع mult به عنوان آرگومان ورودی ارسال شدهاند. همانطور که از قبل میدانید، نام تابع اشارهگری به خود تابع میباشد. در تابع calc نیز با فراخوانی آرگومان fn در واقع داریم تابع ارسالی را فراخوانی مینماییم. حال به مثال زیر توجه کنید که یک تابع را به عنوان خروجی بر میگرداند:
function createFunction ( a , b ) { return function ( c ) { var d = ( a + b ) * c ; return d ; }; } var fn = createFunction ( 10 , 20 ); alert ( fn ( 30 ));
خروجی :
900
تابع createFunction دارای 2 آرگومان ورودی میباشد و یک تابع را با 1 آرگومان ورودی بر میگرداند. در ابتدا تابع createFunction را با آرگومانهای 10 و 20 فراخوانی نمودیم. خروجی این تابع که خود یک تابع با یک آرگومان ورودی میباشد، به عنوان خروجی برگردانده شده و در متغیر fn قرار میگیرد. سپس تابع fn با آرگومان ورودی 30 فراخوانی میگردد که مقادیر 10 و 20 را با هم جمع نموده و در 30 ضرب مینماید.
GUID یا Globally unique identifier یک عدد صحیح 128 بیتی است (بنابراین 2 به توان 128 حالت را میتوان برای آن درنظر گرفت). از لحاظ آماری تولید دو GUID یکسان تقریبا صفر میباشد. به همین جهت از آن با اطمینان میتوان به عنوان یک شناسه منحصربفرد استفاده کرد. برای مثال اگر به لینکهای دانلود فایلها از سایت مایکروسافت دقت کنید، این نوع GUID ها را به وفور میتوانید ملاحظه نمائید. یا زمانیکه قرار است فایلی را که بر روی سرور آپلود شده، ذخیره نمائیم، میتوان نام آنرا یک GUID درنظر گرفت بدون اینکه نگران باشیم آیا فایل آپلود شده بر روی یکی از فایلهای موجود overwrite میشود یا خیر. یا مثلا استفاده از آن در سناریوی بازیابی کلمه عبور در یک سایت. هنگامیکه کاربری درخواست بازیابی کلمه عبور فراموش شده خود را داد، یک GUID برای آن تولید کرده و به او ایمیل میزنیم و در آخر آنرا در کوئری استرینگی دریافت کرده و با مقدار موجود در دیتابیس مقایسه میکنیم. مطمئن هستیم که این عبارت قابل حدس زدن نیست و همچنین یکتا است.
برای تولید GUID ها در دات نت میتوان مانند مثال زیر عمل کرد و خروجیهای دلخواهی را با فرمتهای مختلفی دریافت کرد:
System.Guid.NewGuid().ToString() = 81276701-9dd7-42e9-b128-81c762a172ff
System.Guid.NewGuid().ToString("N") = 489ecfc61ee7403988efe8546806c6a2
System.Guid.NewGuid().ToString("D") = 119201d9-84d9-4126-b93f-be6576eedbfd
System.Guid.NewGuid().ToString("B") = {fd508d4b-cbaf-4f1c-894c-810169b1d20c}
System.Guid.NewGuid().ToString("P") = (eee1fe00-7e63-4632-a290-516bfc457f42)
دو روش برای انجام اینکار وجود دارد
الف) عبارت دریافت شده را به new Guid پاس کنیم. اگر ورودی غیرمعتبر باشد، یک exception تولید خواهد شد.
ب) استفاده از regular expressions جهت بررسی الگوی عبارت وارد شده
پیاده سازی این دو را در کلاس زیر میتوان ملاحظه نمود:
using System;
using System.Text.RegularExpressions;
namespace sample
{
/// <summary>
/// بررسی اعتبار یک گوئید
/// </summary>
public static class CValidGUID
{
/// <summary>
/// بررسی تعیین اعتبار ورودی
/// </summary>
/// <param name="guidString">ورودی</param>
/// <returns></returns>
public static bool IsGuid(this string guidString)
{
if (string.IsNullOrEmpty(guidString)) return false;
bool bResult;
try
{
Guid g = new Guid(guidString);
bResult = true;
}
catch
{
bResult = false;
}
return bResult;
}
/// <summary>
/// بررسی تعیین اعتبار ورودی
/// </summary>
/// <param name="input">ورودی</param>
/// <returns></returns>
public static bool IsValidGUID(this string input)
{
return !string.IsNullOrEmpty(input) &&
new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$").IsMatch(input);
}
}
}
using NUnit.Framework;
using sample;
namespace TestLibrary
{
[TestFixture]
public class TestCValidGUID
{
/*******************************************************************************/
[Test]
public void TestIsGuid1()
{
Assert.IsTrue("81276701-9dd7-42e9-b128-81c762a172ff".IsGuid());
}
[Test]
public void TestIsGuid2()
{
Assert.IsTrue("489ecfc61ee7403988efe8546806c6a2".IsGuid());
}
[Test]
public void TestIsGuid3()
{
Assert.IsTrue("{fd508d4b-cbaf-4f1c-894c-810169b1d20c}".IsGuid());
}
[Test]
public void TestIsGuid4()
{
Assert.IsTrue("(eee1fe00-7e63-4632-a290-516bfc457f42)".IsGuid());
}
[Test]
public void TestIsGuid5()
{
Assert.IsFalse("81276701;9dd7;42e9-b128-81c762a172ff".IsGuid());
}
/*******************************************************************************/
[Test]
public void TestIsValidGUID1()
{
Assert.IsTrue("81276701-9dd7-42e9-b128-81c762a172ff".IsValidGUID());
}
[Test]
public void TestIsValidGUID2()
{
Assert.IsTrue("489ecfc61ee7403988efe8546806c6a2".IsValidGUID());
}
[Test]
public void TestIsValidGUID3()
{
Assert.IsTrue("{fd508d4b-cbaf-4f1c-894c-810169b1d20c}".IsValidGUID());
}
[Test]
public void TestIsValidGUID4()
{
Assert.IsTrue("(eee1fe00-7e63-4632-a290-516bfc457f42)".IsValidGUID());
}
[Test]
public void TestIsValidGUID5()
{
Assert.IsFalse("81276701;9dd7;42e9-b128-81c762a172ff".IsValidGUID());
}
}
}
همانطور که ملاحظه میکنید حالت دوم یعنی استفاده از عبارات باقاعده دو حالت را نمیتواند بررسی کند (مطابق الگوی بکار گرفته شده که البته قابل اصلاح است)، اما روش معمولی استفاده از new Guid ، تمام فرمتهای تولید شده توسط دات نت را پوشش میدهد.
سؤال: آیا میشود دسترسی به فایلهای قرار گرفته در این پوشه عمومی را کنترل کرد؟ به نحوی که فقط کاربران عضو سایت پس از اعتبارسنجی بتوانند آنها را دریافت کنند؟
پاسخ: شاید عنوان کنید که میتوان از تگ location در فایل web.config برای اینکار استفاده کرد:
<location path="Export"> <system.web> <authorization> <deny users="?" /> </authorization> </system.web> </location>
سؤال: آیا راه حلی وجود دارد که بتوان فایلهای استاتیک را صرفنظر از نوع، نگارش و حالت اجرای IIS توسط موتور ASP.NET مدیریت کرد؟
پاسخ: بلی. در ASP.NET MVC با تنظیم یک سطر ذیل، اینکار انجام میشود:
public static void RegisterRoutes(RouteCollection routes) { // ... routes.RouteExistingFiles = true; // ... }
RouteCollection در ASP.NET MVC به کمک امکانات MapPathBasedVirtualPathProvider خود، ابتدا درخواست رسیده را بررسی میکند. اگر این درخواست به یک فایل عمومی اشاره کند، کل سیستم مسیریابی را غیرفعال میکند. اما با تنظیم RouteExistingFiles دیگر این بررسی صورت نخواهد گرفت (به عبارتی در بالا بردن سرعت نمایشی سایت نیز تاثیر گذار خواهد بود؛ چون یکی از بررسیها را حذف میکند).
ایجاد کنترلری به نام پوشهای که قرار است محافظت شود
نام پوشه قرار گرفته در ریشه سایت، Export است. بنابراین برای هدایت درخواستهای رسیده به آن (پس از تنظیم فوق)، نیاز است یک کنترلر جدید را به نام Export نیز ایجاد کنیم:
using System.IO; using System.Web.Mvc; namespace Mvc4RouteExistingFiles.Controllers { public class ExportController : Controller { public ActionResult Index(string id) { if (string.IsNullOrWhiteSpace(id)) { return Redirect("/"); } var fileName= Path.GetFileName(id); var path = Server.MapPath("~/export/"+ fileName); return File(path, System.Net.Mime.MediaTypeNames.Application.Octet, fileName); } } }
برای اینکه بررسی کنیم، آیا واقعا فایلهای استاتیک قرار گرفته در پوشه Export به این کنترلر هدایت میشود یا خیر، یک breakpoint را بر روی سطر اول اکشن متد Index قرار میدهیم. برنامه را اجرا کنید ... کار نخواهد کرد، زیرا مسیر یک فایل فرضی به صورت ذیل:
http://localhost/export/test.pdf
برای حل این مشکل فقط کافی است مسیر یابی متناظری را تعریف کنیم:
using System.Web.Mvc; using System.Web.Routing; namespace Mvc4RouteExistingFiles { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.RouteExistingFiles = true; routes.MapRoute( name: "ExportRoute", url: "Export/{id}", defaults: new { controller = "Export", action = "Index", id = UrlParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
اینبار اگر برنامه را اجرا کنیم، breakpoint ما کار خواهد کرد:
تنظیمات ثانویه پس از فعال سازی RouteExistingFiles
در این حالت با فعال سازی مسیریابی فایلهای موجود، دیگر هیچ فایل استاتیکی به صورت معمول در اختیار کاربران قرار نخواهد گرفت و اگر همانند توضیحات قبل برای آنها کنترلر جداگانهای را تهیه نکنیم، عملا سایت از کار خواهد افتاد.
برای رفع این مشکل، در ابتدای متد RegisterRoutes فوق، تنظیمات ذیل را اضافه کنید تا پوشههای content، scripts و همچنین یک سری فایل با پسوند مشخص، همانند سابق و مستقیما توسط سرور ارائه شوند؛ در غیراینصورت کاربر پیغام 404 را پس از درخواست آنها، دریافت خواهد کرد:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.IgnoreRoute("content/{*pathInfo}"); routes.IgnoreRoute("scripts/{*pathInfo}"); routes.IgnoreRoute("favicon.ico"); routes.IgnoreRoute("{resource}.ico"); routes.IgnoreRoute("{resource}.png"); routes.IgnoreRoute("{resource}.jpg"); routes.IgnoreRoute("{resource}.gif"); routes.IgnoreRoute("{resource}.txt");
توسعه یک پلاگین ساده جیکوئری
الفبای انگلیسی دارای ۲۶ حرف است که بسیاری از این حروف در اکثر زبانها مانند فرانسوی، آلمانی، اسپانیایی و ... مشترک هستند و از این 26 حرف، 5 حرف آن صدا دار میباشد. اکنون قصد داریم در این پست با استفاده از متدهای الحاقی، رشته وارد شده رو برسی و در صورت وجود حروف صدا دار انگلیسی مقدار true یا false را برگردانیم.