نوشتن TagHelperهای سفارشی برای ASP.NET Core
using System.IO; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Html; namespace Sample { /// <summary> /// Html Helper Extensions /// </summary> public static class HtmlHelperExtensions { /// <summary> /// Convert IHtmlContent/TagBuilder to string /// </summary> public static string GetString(this IHtmlContent content) { using (var writer = new StringWriter()) { content.WriteTo(writer, HtmlEncoder.Default); return writer.ToString(); } } } }
معرفی کتابخانه PdfReport
تغییرات مورد نیاز سمت کلاینت جهت فعال سازی جستجو در jqGrid
در سمت کلاینت، در حین تعریف ستونها، ابتدا باید توسط مقدار دهی خاصیت search، ستونهای مشارکت کنندهی در حین جستجو را مشخص کرد:
colModel: [ { name: 'Name', index: 'Name', align: 'right', width: 200, search: true, stype: 'text', searchoptions: { sopt: searchOptions } }, { name: 'Supplier.Id', index: 'Supplier.Id', align: 'right', width: 200, search: true, stype: 'select', edittype: 'select', searchOperators: true, searchoptions: { sopt: searchOptions, dataUrl: '@Url.Action("SuppliersSelect","Home")' } } ],
- stype، روش مقایسهی مقادیر را مشخص میکند. مقدار پیش فرض آن text است و مقادیری مانند int، integer، float، number، numeric، date و datetime را میپذیرد.
- searchoptions برای تنظیم جزئیات نحوهی جستجوی بر روی فیلدها بکار میرود. توسط sopt میتوان آرایهای با مقادیر ذیل را مقدار دهی کرد:
var searchOptions = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'bw', 'bn', 'in', 'ni', 'ew', 'en', 'cn', 'nc'];
البته باید دقت داشت که آرایه فوق کاملترین حالت ممکن است و ضرورتی ندارد تمام حالات را برای یک فیلد تعریف کرد. چون برای مثال جستجو cn یا contains برای مقادیر رشتهای معنا دارد و نه سایر حالات.
- searchoptions گزینههای دیگری را نیز میتواند شامل شود. برای مثال در حین نمایش جستجوی داخل ردیفی یا صفحهی دیالوگ مخصوص آن، قصد داریم فیلد نام تولید کننده را توسط یک drop down نمایش دهیم و نه یک text box پیش فرض. برای این منظور dataUrl مقدار دهی شدهاست.
SuppliersSelect آن به اکشن متد ذیل اشاره میکند که لیست تولید کنندگان را با فرمت لیستی از SelectListItemها به یک partial view تولید کنندهی دراپ داون ارسال میکند:
public ActionResult SuppliersSelect() { var list = ProductDataSource.LatestProducts; var suppliers = list.Select(x => new SelectListItem { Text = x.Supplier.CompanyName, Value = x.Supplier.Id.ToString(CultureInfo.InvariantCulture) }).ToList(); return PartialView("_SelectPartial", suppliers); }
@model IList<SelectListItem> @Html.DropDownList("srch", Model)
- اگر دقت کرده باشید، نام فیلد تولید کننده با Supplier.Id مقدار دهی شدهاست. علت اینجا است که در زمان استفاده از drop down، مقدار Id آیتم انتخابی، به سرور ارسال میشود. به همین جهت کار کردن پویا با Supplier.Id سادهتر خواهد بود.
- برای نمایش دیالوگ و یا نوار ابزار توکار جستجو، میتوان دکمههایی را به فوتر گرید اضافه کرد:
$(document).ready(function () { $('#list').jqGrid({ // ... مانند قبل و توضیحات فوق }) .jqGrid('navGrid', '#pager', { add: false, edit: false, del: false }, {}, // default settings for edit {}, // default settings for add {}, // delete instead that del:false we need this { // search options multipleSearch: true, closeOnEscape: true, closeAfterSearch: true, ignoreCase: true }) .jqGrid('navButtonAdd', "#pager", { caption: "نوار ابزار جستجو", title: "Search Toolbar", buttonicon: 'ui-icon-search', onClickButton: function () { toolbarSearching(); } }); }); function toolbarSearching() { $('#list').filterToolbar({ groupOp: 'OR', defaultSearch: "cn", autosearch: true, searchOnEnter: true, searchOperators: true, // فعال سازی منوی اپراتورها stringResult : true // وجود این سطر سبب میشود تا اپراتورها به سرور ارسال شوند }); }; function singleSearching() { $('#list').searchGrid({ closeAfterSearch: true }); }; function advancedSearching() { $('#list').searchGrid({ multipleSearch: true, closeAfterSearch: true }); };
- با استفاده از متد jqGrid و پارامتر navGrid، در ناحیهی pager گرید، تنظیمات جستجو را فعال کردهایم.
multipleSearch به معنای امکان جستجوی همزمان بر روی بیش از یک فیلد است. closeOnEscape سبب بسته شدن صفحهی دیالوگ جستجو با فشردن دکمهی ESC میشود. اگر closeAfterSearch به true تنظیم نشود، صفحهی دیالوگ جستجو پس از جستجو، در صفحه باقی مانده و بسته نخواهد شد.
- این دکمهی جستجو، جزو موارد توکار jqGrid است. اگر قصد داشته باشیم یک دکمهی سفارشی دیگر را نیز اضافه کنیم، مجددا از متد jqGrid با پارامتر navButtonAdd در ناحیهی pager استفاده خواهیم کرد. کلیک بر روی آن سبب اجرای متد toolbarSearching میشود.
در اینجا حداقل سه نوع جستجو را میتوان فعال کرد:
- filterToolbar که سبب نمایش نوار ابزار جستجو، دقیقا بالای ستونهای جدول میشود.
- searchGrid که صفحهی دیالوگ مستقلی را جهت جستجو به صورت پویا تولید میکند. اگر خاصیت multipleSearch آن به true تنظیم نشود، این جستجو هربار تنها بر روی یک فیلد قابل انجام خواهد بود و برعکس.
- در حالت جستجوی نوار ابزاری، اگر خواص searchOperators و stringResult به true تنظیم شوند، مانند تصویر ذیل، به ازای هر ستون میتوان از عملگرهای مختلفی استفاده کرد. در غیراینصورت جستجوی انجام شده بر اساس groupOp و defaultSearch پیش فرض انجام میشود. یعنی And یا Or تمام موارد تنها در حالت مثلا contains یا تساوی و امثال آن و نه حالت پیشرفتهی انتخاب عملگرها توسط کاربر.
یک نکته
اگر میخواهید صفحهی جستجو در وسط صفحه ظاهر شود، میتوانید از تنظیمات CSS ذیل استفاده کنید:
/* align center search popup in jqgrid */ .ui-jqdialog { display: none; width: 300px; position: absolute; padding: .2em; font-size: 11px; overflow: visible; left: 30% !important; top: 40% !important; }
پردازش سمت سرور جستجوی پویای jqGrid
کدهای سمت سرور، با کدهای استفاده از dynamic LINQ مایکروسافت یکی است. با این تفاوت که اینبار قسمت where این کوئری نیز پویا میباشد. پیشتر قسمت order by را پویا پردازش کرده بودیم. برای ساخت where پویا که در dynamic LINQ به خوبی پشتیبانی میشود، باید ابتدا ساختار اطلاعات ارسالی به سرور را آنالیز کنیم:
//single field search //_search=true&nd=1403935889318&rows=10&page=1&sidx=Id&sord=asc&searchField=Id&searchString=4444&searchOper=eq&filters= //multi-field search //_search=true&nd=1403935941367&rows=10&page=1&sidx=Id&sord=asc&filters=%7B%22groupOp%22%3A%22AND%22%2C%22rules%22%3A%5B%7B%22field%22%3A%22Id%22%2C%22op%22%3A%22eq%22%2C%22data%22%3A%2244%22%7D%2C%7B%22field%22%3A%22SupplierID%22%2C%22op%22%3A%22eq%22%2C%22data%22%3A%221%22%7D%5D%7D&searchField=&searchString=&searchOper= // filters -> {"groupOp":"AND","rules":[{"field":"All","op":"cn","data":"fffff"},{"field":"Price","op":"bn","data":"ffff"}]} //toolbar search //_search=true&nd=1403935593036&rows=10&page=1&sidx=Id&sord=asc&Id=2&Name=333&SupplierID=1&CategoryID=1&Price=44
- در تمام این حالات پارامتر _search مساوی true است (تفاوت آن با درخواست اطلاعات معمولی).
- در حالت جستجوی نوار ابزاری، اگر گزینههای searchOperators و stringResult به true تنظیم نشوند، حالت toolbar search فوق را شاهد خواهیم بود. در غیراینصورت به حالت multi-field search سوئیچ میشود.
- در حالت جستجوی تک فیلدی توسط صفحه دیالوگ جستجوی jqGrid، فیلد در حال جستجو توسط searchField و مقدار آن توسط searchString به سرور ارسال شدهاند. مابقی پارامترها نال هستند.
- در حالت جستجوی چند فیلدی توسط صفحه دیالوگ جستجوی jqGrid، اینبار filters مقدار دهی شدهاست و سایر پارامترها نال هستند. مقدار filters ارسالی، در حقیقت یک شیء JSON است با ساختار کلی ذیل:
{ "groupOp": "AND", "groups" : [ { "groupOp": "OR", "rules": [ { "field": "name", "op": "eq", "data": "England" }, { "field": "id", "op": "le", "data": "5"} ] } ], "rules": [ { "field": "name", "op": "eq", "data": "Romania" }, { "field": "id", "op": "le", "data": "1"} ] }
public class SearchFilter { public string groupOp { set; get; } public List<SearchGroup> groups { set; get; } public List<SearchRule> rules { set; get; } } public class SearchRule { public string field { set; get; } public string op { set; get; } public string data { set; get; } public override string ToString() { return string.Format("'{0}' {1} '{2}'", field, op, data); } } public class SearchGroup { public string groupOp { set; get; } public List<SearchRule> rules { set; get; } }
اگر این موارد را کنار هم قرار دهیم، به متدی عمومی ApplyFilter با امضای ذیل خواهیم رسید.
public IQueryable<T> ApplyFilter<T>(IQueryable<T> query, bool _search, string searchField, string searchString, string searchOper, string filters, NameValueCollection form)
پس از آن، تغییراتی که در کدهای متد GetProducts باید اعمال شوند به صورت ذیل است:
[HttpPost] public ActionResult GetProducts(string sidx, string sord, int page, int rows, bool _search, string searchField, string searchString, string searchOper, string filters) { var list = ProductDataSource.LatestProducts; var pageIndex = page - 1; var pageSize = rows; var totalRecords = list.Count; var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize); var productsQuery = list.AsQueryable(); productsQuery = new JqGridSearch().ApplyFilter(productsQuery, _search, searchField, searchString, searchOper, filters, this.Request.Form); var productsList = productsQuery.OrderBy(sidx + " " + sord) .Skip(pageIndex * pageSize) .Take(pageSize) .ToList(); var productsData = new JqGridData { Total = totalPages, Page = page, Records = totalRecords, Rows = (productsList.Select(product => new JqGridRowData { Id = product.Id, RowCells = new List<string> { product.Id.ToString(CultureInfo.InvariantCulture), product.Name, product.Supplier.CompanyName, product.Category.Name, product.Price.ToString(CultureInfo.InvariantCulture) } })).ToArray() }; return Json(productsData, JsonRequestBehavior.AllowGet); }
- نوع متد به HttpPost تغییر کردهاست. این مورد برای ارسال اطلاعات حجیم جستجوها به سرور ضروری است و بهتر است از حالت Get استفاده نشود.
این حالت در سمت کلاینت نیز باید تنظیم شود:
$('#list').jqGrid({ //url access method type mtype: 'POST',
productsQuery = new JqGridSearch().ApplyFilter(productsQuery, _search, searchField, searchString, searchOper, filters, this.Request.Form);
برای مطالعه بیشتر
جستجوی تک فیلدی
جستجوی نوار ابزاری
جستجوی چند فیلدی
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid03.zip
<input type="file" multiple="multiple" name="FileUpload1" id="FileUpload1" />
<asp:FileUpload runat="server" ID="FileUploadMultiple" AllowMultiple="true" />
در صورتیکه از این روش در پروژههایتان استفاده کنید فقط کافیست با یک حلقه تمامی کنترلهای مورد نظر را پیمایش و هر کدام از فایلها را آپلود و ذخیره نمایید.
<asp:FileUpload runat="server" ID="FileUploadMultiple" AllowMultiple="true" /> <asp:Button runat="server" ID="btnUlpad" Text="Upload" OnClick="btnUlpad_Click" /> <asp:Label runat="server" ID="lblMessage"></asp:Label>
int Count = 0; foreach (var item in FileUploadMultiple.PostedFiles) { string Extension = Path.GetExtension(item.FileName); string FileName = new Random().Next(1, 50).ToString()+Extension; item.SaveAs(Server.MapPath("~")+"//File//"+FileName); Count++; } if (Count == FileUploadMultiple.PostedFiles.Count) lblMessage.Text = string.Format("فایلهای انتخابی با موفقیت آپلود شدند"); else lblMessage.Text = string.Format("{0} از {1} فایل با موفقیت آپلود شد", Count, FileUploadMultiple.PostedFiles.Count);
استفاده از Log4Net جهت ثبت خروجیهای SQL حاصل از NHibernate
هنگام استفاده از NHibernate، پس از افزودن ارجاعات لازم به اسمبلیهای مورد نیاز آن به برنامه، یکی از اسمبلیهایی که به پوشه build برنامه به صورت خودکار کپی میشود، فایل log4net.dll است (حتی اگر ارجاعی را به آن اضافه نکرده باشیم) که جهت ثبت وقایع مرتبط با NHibernate مورد استفاده قرار میگیرد. خوب اگر مجبوریم که این وابستگی کتابخانه NHibernate را نیز در پروژههای خود داشته باشیم، چرا از آن استفاده نکنیم؟!
شرح مفصل استفاده از این کتابخانه سورس باز را در سایت اصلی آن میتوان مشاهده کرد:
برای اینکه از این کتابخانه در برنامه خود جهت ثبت عبارات SQL تولیدی توسط NHibernate استفاده کنیم، باید مراحل زیر طی شوند:
الف) ارجاعی را به اسمبلی log4net.dll اضافه نمائید (کنار اسمبلی NHibernate در پوشه build برنامه باید موجود باشد)
ب) فایل app.config برنامه را (برنامه ویندوزی) به صورت زیر ویرایش کرده و چند سطر مربوطه را اضافه نمائید (در مورد برنامههای وب هم به همین شکل است. configSections فایل web.config تنظیم شده و سپس تنظیمات log4net را قبل از بسته شدن تگ configuration اضافه نمائید ) :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>
<log4net>
<appender name="rollingFile"
type="log4net.Appender.RollingFileAppender,log4net" >
<param name="File" value="NHibernate_Log.txt" />
<param name="AppendToFile" value="true" />
<param name="DatePattern" value="yyyy.MM.dd" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="500KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<conversionPattern value="%d %p %m%n" />
</layout>
</appender>
<logger name="NHibernate.SQL">
<level value="ALL" />
<appender-ref ref="rollingFile" />
</logger>
</log4net>
</configuration>
log4net.Config.XmlConfigurator.Configure();
یا در یک برنامه از نوع WinForms تنها کافی است سطر زیر را به فایل AssemblyInfo.cs برنامه اضافه کرد:
// Configure log4net using the .config file
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
د) هنگام استفاده از کتابخانه Fluent NHibernate حتما باید متد ShowSql در جایی که دیتابیس برنامه را تنظیم میکنیم (Fluently.Configure().Database) ذکر گردد (که نمونه آنرا در مثالهای قسمتهای قبل ملاحظه کردهاید).
توضیحاتی در مورد تنظیمات فوق:
configSections حتما باید در ابتدای فایل app.config ذکر شود و گرنه برنامه کار نخواهد کرد.
سپس کانکشن استرینگ مورد استفاده در قسمت کانفیگ برنامه ذکر شده است.
در ادامه تنظیمات استاندارد مربوط به log4net را مشاهده میکنید.
در تنظیمات این کتابخانه، appender مشخص کننده محل ثبت وقایع است. زمانیکه که از RollingFileAppender استفاده کنیم، اطلاعات را در یک سری فایل ذخیره خواهد کرد (امکان ثبت وقایع در EventLog ویندوز، ارسال از طریق ایمیل و غیره نیز میسر است که جهت توضیحات بیشتر میتوان به مستندات آن رجوع نمود).
سپس نام فایلی که اطلاعات وقایع در آن ثبت خواهند شد ذکر شده است (برای مثال NHibernate_Log.txt)، در ادامه مشخص گردیده که اطلاعات باید هر بار به این فایل Append و اضافه شوند. سطرهای بعدی مشخص میکنند که هر زمانیکه این لاگ فایل به 10 مگابایت رسید، یک فایل جدید تولید کن و هر بار 10 فایل آخر را نگه دار و مابقی فایلهای قدیمی را حذف کن.
در قسمت PatternLayout مشخصات میکنیم که خروجی ثبت شده با چه فرمتی باشد. برای مثال یک سطر خروجی مطابق با تنظیمات فوق به شکل زیر خواهد بود:
2009-10-18 20:03:54,187 DEBUG INSERT INTO [Student] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = 'Vahid'
توسط appender-ref آن appender ایی را که در ابتدای کار تعریف و تنظیم کردیم، مشخص خواهیم کرد.
اگر هم با برنامه نویسی بخواهیم اطلاعاتی را به این لاگ فایل اضافه کنیم تنها کافی است بنویسیم:
log4net.LogManager.GetLogger("NHibernate.SQL").Info("test1");
اطلاعات بیشتر
ادامه دارد ...
تعیین اجباری بودن یا نبودن ستونها در EF Core
به صورت پیش فرض در EF Core، هر نوع CLR ایی که نال پذیر باشد، به صورت یک ستون اختیاری در نظر گرفته میشود؛ مانند:
string, int?, byte[]
int, decimal, bool, DateTime
برای لغو اختیاری بودن یک خاصیت نال پذیر میتوان از ویژگی Required استفاده کرد:
[Required] public string Url { get; set; }
و یا معادل Fluent API آن با استفاده از ذکر متد IsRequired است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); }
کار با رشتهها در EF Core
ذکر یک خاصیت رشتهای به این صورت:
public string FirstName { get; set; }
[StringLength(450)] public string FirstName { get; set; } [MaxLength(450)] public string LastName { get; set; } [MaxLength] public string Address { get; set; }
برای تعیین صریح یک فیلد رشتهای به حداکثر مقدار آن بهتر است ویژگی MaxLength را بدون ذکر پارامتری قید کرد. این مورد جهت سازگاری با بانکهای اطلاعاتی مختلف ضروری است.
معادل این تنظیمات با روش Fluent API به صورت زیر است:
برای تعیین صریح طول یک فیلد رشتهای:
modelBuilder.Entity<Person>() .Property(x => x.Address) .HasMaxLength(450);
modelBuilder.Entity<Person>() .Property(x => x.Address) .HasColumnType("nvarchar(max)");
[Column(TypeName = "varchar")] [MaxLength] public string Address { get; set; }
نکتهای در مورد تغییر نوع خواص: اگر از متد HasColumnType و یا ویژگی Column به نحو فوق استفاده کردید، نیاز است طول رشته را صریحا مشخص کنید. در غیر اینصورت در حین migration خطای ذیل را دریافت خواهید کرد:
Data type 'varchar' is not supported in this form. Either specify the length explicitly in the type name, for example as 'varchar(16)', or remove the data type and use APIs such as HasMaxLength to allow EF choose the data type.
[Column(TypeName = "varchar(max)")]
نکتهای در مورد ایندکسها: در قسمت قبل عنوان شد که میتوان بر روی خواص، ایندکس منحصربفرد اعمال کرد. در مورد رشتهها در SQL Server، اگر طول فیلد مدنظر حداکثر تا 900 بایت باشد، یک چنین کاری را میتوان انجام داد. البته این محدودیت 900 بایتی تا SQL Server 2014 وجود دارد. این سقف در SQL Server 2016 به 1700 بایت افزایش یافتهاست (900bytes for a clustered index. 1,700 for a nonclustered index). بنابراین چون نوع پیش فرض ستونهای رشتهای، یونیکد و nvarchar درنظر گرفته میشود، حداکثر طول امنی را که میتوان برای آن تعریف کرد، مساوی 450 است (نصف 900 بایت). به همین جهت ذکر ایندکس منحصربفرد بر روی یک ستون رشتهای، باید به همراه ذکر اجباری حداکثر طول مساوی 450 آن باشد.
کار با اعداد در EF Core
کلاس نمونهای را با ساختار ذیل درنظر بگیرید:
public class Person { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } [StringLength(450)] public string FirstName { get; set; } [MaxLength(450)] public string LastName { get; set; } //[Column(TypeName = "varchar")] [MaxLength] public string Address { get; set; } //bit public bool IsActive { get; set; } //tiny Int public byte Age { get; set; } //small Int public short Short { get; set; } //int public int Int32 { get; set; } //Big int public long Long { get; set; } }
همانطور که ملاحظه میکنید، نوع bool دات نت به نوع bit در SQL Server، نوع long به bigint، نوع short به smallint، نوع int به int و نوع byte به tinyint نگاشت شدهاند.
نکتهای در مورد اعداد اعشاری: توصیه شدهاست در تعاریف موجودیتهای خود بهتر است از نوعهای float و یا double استفاده نکنید. برای کار با اعداد اعشاری از نوع decimal استفاده نمائید تا بتوانید از قابلیت مقایسهی دقیق آنها استفاده کنید. اطلاعات بیشتر: «روش صحیح مقایسه دو عدد اعشاری با هم»
کار با تاریخ در EF Core
اگر به تصویر فوق دقت کنید، نوع DateTime دات نت به datetime2 در سمت SQL Server ترجمه شدهاست:
CREATE TABLE [dbo].[Persons]( [DateAdded] [datetime2](7) NULL, [DateUpdated] [datetime2](7) NULL,
اطلاعات بیشتر: «کنترل نوعهای داده با استفاده از EF در SQL Server»
به علاوه در دات نت نوع DateTime از نوع value type است. بنابراین همانطور که در ابتدای بحث نیز عنوان شد، مقدار دهی آن اجباری است؛ مگر آنکه آنرا نال پذیر تعریف کنید.
کار با مباحث همزمانی در EF Core
EF Core به صورت پیش فرض، فرض میکند رکوردی را که با آن در حال کار هستید، توسط هیچ کاربر دیگری در شبکه تغییر نیافتهاست و تغییرات شما را در حین فراخوانی متد SaveChanges ذخیره میکند. اگر علاقمند هستید که EF Core در صورت تغییر مقدار خاصیت خاصی توسط سایر کاربران، این مساله را با صدور استثنایی به شما اطلاع رسانی کند، از ویژگی ConcurrencyCheck
[ConcurrencyCheck] public string Name { set; get; }
modelBuilder.Entity<Person>() .Property(p => p.Name) .IsConcurrencyToken();
اگر علاقمند هستید که تمام فیلدهای جدول تحت نظر قرارگیرند، میتوان از روش ویژهای به نام Timestamp/row version استفاده کرد:
[Timestamp] public byte[] Timestamp { get; set; }
modelBuilder.Entity<Blog>() .Property(p => p.Timestamp) .ValueGeneratedOnAddOrUpdate() .IsConcurrencyToken();
در این حالت در حین به روز رسانی یک چنین رکوردی، اگر از زمان کوئری آن (یافتن رکورد) و ذخیره سازی آن، شخص دیگری آنرا تغییر داده باشد، به علت عدم تطابق Timestamp ها، عملیات به روز رسانی باشکست روبرو شده و یک استثناء صادر میشود.
در ادامه بررسی تصویر امنیتی سایت مواردی که باید پیاده سازی شود برای مورد اول میتوان کلاس زیر را در نظر گرفت که متدهایی برای تولید اعداد بصورت تصادفی در بین بازه معرفی شده را بازگشت میدهد:
// RandomGenerator.cs using System; using System.Security.Cryptography; namespace PersianCaptchaHandler { public class RandomGenerator { private static readonly byte[] Randb = new byte[4]; private static readonly RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider(); public static int Next() { Rand.GetBytes(Randb); var value = BitConverter.ToInt32(Randb, 0); if (value < 0) value = -value; return value; } public static int Next(int max) { Rand.GetBytes(Randb); var value = BitConverter.ToInt32(Randb, 0); value = value%(max + 1); if (value < 0) value = -value; return value; } public static int Next(int min, int max) { var value = Next(max - min) + min; return value; } } }
// NumberToString.cs namespace PersianCaptchaHandler { public class NumberToString { #region Fields private static readonly string[] Yakan = new[] { "صفر", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه" }; private static readonly string[] Dahgan = new[] { "", "", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" }; private static readonly string[] Dahyek = new [] { "ده", "یازده", "دوازده", "سیزده", "چهارده", "پانزده", "شانزده", "هفده", "هجده", "نوزده" }; private static readonly string[] Sadgan = new [] { "", "یکصد", "دوصد", "سیصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد", "نهصد" }; private static readonly string[] Basex = new [] { "", "هزار", "میلیون", "میلیارد", "تریلیون" }; #endregion private static string Getnum3(int num3) { var s = ""; var d12 = num3 % 100; var d3 = num3 / 100; if (d3 != 0) s = Sadgan[d3] + " و "; if ((d12 >= 10) && (d12 <= 19)) { s = s + Dahyek[d12 - 10]; } else { var d2 = d12 / 10; if (d2 != 0) s = s + Dahgan[d2] + " و "; var d1 = d12 % 10; if (d1 != 0) s = s + Yakan[d1] + " و "; s = s.Substring(0, s.Length - 3); } return s; } public static string ConvertIntNumberToFarsiAlphabatic(string snum) { var stotal = ""; if (snum == "0") return Yakan[0]; snum = snum.PadLeft(((snum.Length - 1) / 3 + 1) * 3, '0'); var l = snum.Length / 3 - 1; for (var i = 0; i <= l; i++) { var b = int.Parse(snum.Substring(i * 3, 3)); if (b != 0) stotal = stotal + Getnum3(b) + " " + Basex[l - i] + " و "; } stotal = stotal.Substring(0, stotal.Length - 3); return stotal; } } }
using System; using System.IO; using System.Security.Cryptography; using System.Text; namespace PersianCaptchaHandler { public class Encryptor { #region constraints private static string Password { get { return "Mehdi"; } } private static string Salt { get { return "Payervand"; } } #endregion public static string Encrypt(string clearText) { // Turn text to bytes var clearBytes = Encoding.Unicode.GetBytes(clearText); var pdb = new PasswordDeriveBytes(Password, Encoding.Unicode.GetBytes(Salt)); var ms = new MemoryStream(); var alg = Rijndael.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write); cs.Write(clearBytes, 0, clearBytes.Length); cs.Close(); var encryptedData = ms.ToArray(); return Convert.ToBase64String(encryptedData); } public static string Decrypt(string cipherText) { // Convert text to byte var cipherBytes = Convert.FromBase64String(cipherText); var pdb = new PasswordDeriveBytes(Password, Encoding.Unicode.GetBytes(Salt)); var ms = new MemoryStream(); var alg = Rijndael.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write); cs.Write(cipherBytes, 0, cipherBytes.Length); cs.Close(); var decryptedData = ms.ToArray(); return Encoding.Unicode.GetString(decryptedData); } } }
و نیز برای اعتبار سنجی عدد دریافتی از کاربر میتوان از عبارت با قاعده زیر استفاده کرد:
// Utils.cs using System.Text.RegularExpressions; namespace PersianCaptchaHandler { public class Utils { private static readonly Regex NumberMatch = new Regex(@"^([0-9]*|\d*\.\d{1}?\d*)$", RegexOptions.Compiled); public static bool IsNumber(string number2Match) { return NumberMatch.IsMatch(number2Match); } } }
<add verb="GET" path="/captcha/" type="PersianCaptchaHandler.CaptchaHandler, PersianCaptchaHandler, Version=1.0.0.0, Culture=neutral" />
<!-- ASPX --> <dl> <dt>تصویر امنیتی</dt> <dd> <asp:Image ID="imgCaptchaText" runat="server" AlternateText="CaptchaImage" /> <asp:HiddenField ID="hfCaptchaText" runat="server" /> <asp:ImageButton ID="btnRefreshCaptcha" runat="server" ImageUrl="/img/refresh.png" OnClick="btnRefreshCaptcha_Click" /> <br /> <asp:TextBox ID="txtCaptcha" runat="server" AutoCompleteType="Disabled"></asp:TextBox> </dd> <dd> <asp:Button ID="btnSubmit" runat="server" Text="ثبت" OnClick="btnSubmit_Click" /> </dd> </dl> <asp:Label ID="lblMessage" runat="server"></asp:Label>
protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) SetCaptcha(); } private void SetCaptcha() { lblMessage.Text = txtCaptcha.Text = string.Empty; var newNumber = RandomGenerator.Next(100, 999) ; var farsiAlphabatic = NumberToString.ConvertIntNumberToFarsiAlphabatic(newNumber.ToString()); hfCaptchaText.Value = HttpUtility .UrlEncode( Encryptor.Encrypt( farsiAlphabatic ) ); txtCaptcha.Text = string.Empty; imgCaptchaText.ImageUrl = "/captcha/?text=" + hfCaptchaText.Value; }
private string GetCaptcha() { var farsiAlphabatic = NumberToString.ConvertIntNumberToFarsiAlphabatic(txtCaptcha.Text); var encryptedString = HttpUtility .UrlEncode( Encryptor.Encrypt( farsiAlphabatic ) ); return encryptedString; } private bool ValidateUserInputForLogin() { if (!Utils.IsNumber(txtCaptcha.Text)) { lblMessage.Text = "تصویر امنیتی را بطور صحیح وارد نکرده اید"; return false; } var strGetCaptcha = GetCaptcha(); var strDecodedVAlue = hfCaptchaText.Value; if (strDecodedVAlue != strGetCaptcha) { lblMessage.Text = "کلمه امنیتی اشتباه است"; SetCaptcha(); return false; } return true; } protected void btnSubmit_Click(object sender, EventArgs e) { if (!ValidateUserInputForLogin()) return; lblMessage.Text = "کلمه امنیتی درست است"; } protected void btnRefreshCaptcha_Click(object sender, ImageClickEventArgs e) { SetCaptcha(); }
Angular 5.2 منتشر شد
<script> let src = 'https://svelte.dev/tutorial/image.gif'; let name = 'Rick Astley'; </script> <img src={src} alt="{name} dancing">
- نکته اول : اگر در تگ img مقدار alt را وارد نکنیم و یا alt در این تگ وجود نداشته باشد، یک هشدار توسط کامپایلر svelte برای ما با عنوان <img> element should have an alt attribute> ایجاد میشود. زمان ساخت یک برنامه بسیار مهم است تا قوانین نوشتن یک کد html خوب را رعایت کنیم تا برای تمامی کاربران احتمالی برنامه قابل استفاده باشد. در همین مثال با ایجاد یک هشدار Svelte تلاش میکند که ما را از اشتباه در نوشتن کد html مطلع سازد.
- نکته دوم : اگر نام یک آبجکت تعریف شده و یک attribute، برابر باشد میتوانیم از نسخه کوتاه شده یا Shorthand attributes در svelte استفاده کنیم. به طور مثال در مثال بالا میتوانیم از کد زیر در خط 6 استفاده کنیم.
<img {src} alt="{name} dancing">
<script> export let siteName = "dotnettips"; </script> <p>this is a nested component for third tutorial on {siteName}</p>
<script> import Nested from "./Nested.svelte"; export let name; </script> <h1>Hello {name}!</h1> <Nested siteName="dotnettips.info" />
Hello world! this is a nested component for third tutorial on dotnettips.info
در مثال بالا ما یک کامپوننت جدید را ایجاد کرده و از طریق دستور import به App.svelte اضافه کردیم. نکتهای که در اینجا وجود دارد، نحوه مقدار دهی props در کامپوننتها است. اگر به خط 9 دقت کنیم، کامپوننت ما از طریق تگ جدیدی با نام (Nested) به بدنه html برنامه اضافه شده است که یک attribute به نام siteName دارد. siteName متغیر export شده در کامپوننت Nested.svelte است که در کامپوننتها به این صورت مقدار دهی میشود. قبلا نحوه مقدار دهی این خصیصهها را در فایلهای جاوا اسکریپت مشاهده کرده بودیم. نکته دیگری که باید به آن دقت داشت این است که خصیصه siteName مقدار پیش فرض dotnettips را در Nested.svelte به خود اختصاص داده بود. به همین جهت اگر ما siteName را هنگام استفاده از کامپوننت مقدار دهی نکنیم، از مقدار پیش فرض خود استفاده خواهد کرد. ولی اینجا ما با مقدار دهی آن، siteName را به dotnettips.info تغییر دادهایم.
نکته مهم : دقت داشته باشید کامپوننتهای شما همیشه باید با حروف بزرگ شروع شوند؛ به طور مثال در صورت نوشتن <nested/> محتوای کامپوننت نمایش داده نخواهد شد. svelte، از طریق زیر نظر گرفتن حروف کوچک و بزرگ در ابتدای تگها، بین تگهای html و کامپوننتها تمایز قائل میشود.
Spread props :
تا اینجا به صورت خلاصه با props یا خصیصهها آشنا شدهاید و دیدیم که با export کردن یک متغیر در یک کامپوننت، میتوانیم آن را هنگام استفاده مقدار دهی نماییم. برای اینکه تمرینی هم باشد با توجه به مطالبی که تاکنون گفته شده، پروژهی جدیدی را ایجاد کنید و محتوای App.svelte را مانند کد زیر تغییر دهید.
<script> import Info from './Info.svelte'; const pkg = { name: 'svelte', version: 3, speed: 'blazing', website: 'https://svelte.dev' }; </script> <Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/>
همانطور که در خط دوم کد میبینید، کامپوننتی به نام Info.svelte به این بخش اضافه شدهاست. این کامپوننت را با محتوای زیر ایجاد نمایید:
<script> export let name; export let version; export let speed; export let website; </script> <p> The <code>{name}</code> package is {speed} fast. Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a> and <a href={website}>learn more here</a> </p>
اگر برنامه را اجرا کنید یک چنین خروجی را مشاهده خواهید کرد:
The svelte package is blazing fast. Download version 3 from npm and learn more here
<Info {...pkg}/>
<script> let count = 0; function handleClick() { count += 1; } </script> <p>Count : {count}</p> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button>
<script> let count = 0; let doubled = count * 2; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button> <p>{count} doubled is {doubled}</p>
$: doubled = count * 2;
$: console.log(`the count is ${count}`);
$: { console.log(`the count is ${count}`); alert(`I SAID THE COUNT IS ${count}`); }
$: if (count >= 10) { alert(`count is dangerously high!`); count = 9; }
<script> let numbers = [1, 2, 3, 4]; function addNumber() { let newNumber = numbers.length + 1; numbers.push(newNumber); } $: sum = numbers.reduce((t, n) => t + n, 0); </script> <p>{numbers.join(' + ')} = {sum}</p> <button on:click={addNumber}>Add a number</button>
function addNumber() { let newNumber = numbers.length + 1; numbers.push(newNumber); numbers = numbers; }
function addNumber() { let newNumber = numbers.length + 1; numbers = [...numbers, newNumber]; }
مروری بر Two way bindings :
<script> let name = ""; function updateName(event) { name = event.target.value; } </script> <h4>My Name Is {name}</h4> <input value={name} on:input={updateName} />
<script> let name = ""; </script> <h4>My Name Is {name}</h4> <input bind:value={name} />
{#if condition} <!-- you html codes ... --> {/if}
<script> let user = { loggedIn: false }; function toggle() { user.loggedIn = !user.loggedIn; } </script> {#if user.loggedIn} <button on:click={toggle}> Log out </button> {/if} {#if !user.loggedIn} <button on:click={toggle}> Log in </button> {/if}
{#if condition} <!-- you html code when condition is true --> {:else} <!-- you html code when condition is false --> {/if}
{#if user.loggedIn} <button on:click={toggle}> Log out </button> {:else} <button on:click={toggle}> Log in </button> {/if}
{#if condition} <!-- you html code when condition is true --> {:else if condition2} <!-- you html code when condition2 is true --> {:else} <!-- you html code when condition and condition2 are false --> {/if}
{#each list as item} <!-- you html code per each item in list --> {/each}
<script> let cats = [ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' } ]; </script> <h1>The Famous Cats of YouTube</h1> <ul> {#each cats as cat} <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}"> {cat.name} </a></li> {/each} </ul>
<ul> {#each cats as {id,name}} <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {name} </a></li> {/each} </ul>
<ul> {#each cats as { id, name }, i} <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {i + 1}: {name} </a></li> {/each} </ul>
نکته : در این بخش من سعی کردم تا حدودی به ترتیب بخش آموزشی خود وبسایت Svelte، موارد را بیان کنم؛ ولی با توجه به اینکه شاید دوستان ترجیح بدهند روش آموزشی خود آن وبسایت که امکان تغییر و نوشتن کد را هم محیا کرده است، امتحان کنند لینک آن را به اشتراک میگذارم.