using System.Data.Entity; using System.Threading.Tasks; using Microsoft.AspNet.Identity.EntityFramework; using SmartMarket.Core.Domain.Members; using SmartMarket.Data; namespace SmartMarket.Services.Members { /// <summary> /// The ApplicationUserStore Class /// </summary> public class ApplicationUserStore : UserStore<User, Role, int, UserLogin, UserRole, UserClaim>, IApplicationUserStore { #region Fields (1) private readonly IDbSet<User> _userStore; #endregion Fields #region Constructors (2) /// <summary> /// Initializes a new instance of the <see cref="ApplicationUserStore" /> class. /// </summary> /// <param name="dbContext">The database context.</param> public ApplicationUserStore(DbContext dbContext) : base(dbContext) { } /// <summary> /// Initializes a new instance of the <see cref="ApplicationUserStore"/> class. /// </summary> /// <param name="context">The context.</param> public ApplicationUserStore(IdentityDbContext context) : base(context) { _userStore = context.Set<User>(); } #endregion Constructors #region Methods (2) // Public Methods (2) /// <summary> /// Adds to previous passwords asynchronous. /// </summary> /// <param name="user">The user.</param> /// <param name="password">The password.</param> /// <returns></returns> public Task AddToPreviousPasswordsAsync(User user, string password) { user.PreviousUserPasswords.Add(new PreviousPassword { UserId = user.Id, PasswordHash = password }); return UpdateAsync(user); } /// <summary> /// Finds the by identifier asynchronous. /// </summary> /// <param name="userId">The user identifier.</param> /// <returns></returns> public override Task<User> FindByIdAsync(int userId) { return Task.FromResult(_userStore.Find(userId)); } #endregion Methods /// <summary> /// Creates the asynchronous. /// </summary> /// <param name="user">The user.</param> /// <returns></returns> public override async Task CreateAsync(User user) { await base.CreateAsync(user); await AddToPreviousPasswordsAsync(user, user.PasswordHash); } } }
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Mvc; namespace SecurityModule { public class AllowUploadSpecialFilesOnlyAttribute : ActionFilterAttribute { readonly List<string> _toFilter = new List<string>(); readonly string _extensionsWhiteList; public AllowUploadSpecialFilesOnlyAttribute(string extensionsWhiteList) { if (string.IsNullOrWhiteSpace(extensionsWhiteList)) throw new ArgumentNullException("extensionsWhiteList"); _extensionsWhiteList = extensionsWhiteList; var extensions = extensionsWhiteList.Split(','); foreach (var ext in extensions.Where(ext => !string.IsNullOrWhiteSpace(ext))) { _toFilter.Add(ext.ToLowerInvariant().Trim()); } } bool canUpload(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) return false; var ext = Path.GetExtension(fileName.ToLowerInvariant()); return _toFilter.Contains(ext); } public override void OnActionExecuting(ActionExecutingContext filterContext) { var files = filterContext.HttpContext.Request.Files; foreach (string file in files) { var postedFile = files[file]; if (postedFile == null || postedFile.ContentLength == 0) continue; if (!canUpload(postedFile.FileName)) throw new InvalidOperationException( string.Format("You are not allowed to upload {0} file. Please upload only these files: {1}.", Path.GetFileName(postedFile.FileName), _extensionsWhiteList)); } base.OnActionExecuting(filterContext); } } }
توضیحات کدهای فوق:
برای تهیه فیلتر محدود سازی نوع فایلهای قابل ارسال به سرور، با ارث بری از ActionFilterAttribute شروع خواهیم کرد. سپس با تحریف متد OnActionExecuting آن، توسط filterContext.HttpContext.Request.Files میتوان به کلیه فایلهای درحال ارسال به سرور در طی درخواست جاری، دسترسی یافت.
به این ترتیب از طریق مقدار خاصیت postedFile.FileName میتوان به پسوند فایل در حال ارسال رسید و بر این اساس امکان ارسال فایلهای غیرمجاز را در نیمه راه با صدور یک استثناء سلب کرد.
برای استفاده از این فیلتر سفارشی تهیه شده نیز میتوان به نحو زیر عمل کرد:
[AllowUploadSpecialFilesOnly(".jpg,.gif,.png")] public ActionResult ImageUpload(HttpPostedFileBase file)
یک نکته تکمیلی:
اگر کاربر قرار است تنها تصویر ارسال کند، بررسی پسوند فایل لازم است اما کافی نیست. برای این منظور میتوان از کلاس Image واقع شده در فضای نام System.Drawing نیز کمک گرفت:
public static bool IsImageFile(HttpPostedFileBase photoFile) { using (var img = Image.FromStream(photoFile.InputStream)) { return img.Width > 0; } }
نحوه تبدیل تاریخ میلادی به شمسی
using Persia; namespace Iris.Utilities.DateAndTime { public class DateAndTime { public static DateTime GetDateTime() { return DateTime.Now; } public static string ConvertToPersian(DateTime dateTime, string mod = "") { SolarDate solar = Calendar.ConvertToPersian(dateTime); return string.IsNullOrEmpty(mod) ? solar.ToString() : solar.ToString(mod); } } }
using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; namespace GSD.Globalization { /// <summary> /// <Publisher>http://www.Sayan.ir</Publisher> /// <Author>Maziar Rezaie</Author> /// </summary> public class PersianCulture : CultureInfo { private readonly Calendar cal; private readonly Calendar[] optionals; /// <summary> /// کد رو بخوان تا بفهمی /// </summary> /// <param name="cultureName">fa-IR</param> /// <param name="useUserOverride">true</param> /// <remarks>لطفا در هنگام استفاده به سایت سایان اشاره کنید.</remarks> public PersianCulture() : this("fa-IR", true) { } public PersianCulture(string cultureName, bool useUserOverride) : base(cultureName, useUserOverride) { //Temporary Value for cal. cal = base.OptionalCalendars[0]; //populating new list of optional calendars. var optionalCalendars = new List<Calendar>(); optionalCalendars.AddRange(base.OptionalCalendars); optionalCalendars.Insert(0, new PersianCalendar()); Type formatType = typeof(DateTimeFormatInfo); Type calendarType = typeof(Calendar); PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars", BindingFlags.Instance | BindingFlags.NonPublic); //populating new list of optional calendar ids var newOptionalCalendarIDs = new Int32[optionalCalendars.Count]; for (int i = 0; i < newOptionalCalendarIDs.Length; i++) newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null); optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs); optionals = optionalCalendars.ToArray(); cal = optionals[0]; DateTimeFormat.Calendar = optionals[0]; DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" }; DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" }; DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" }; DateTimeFormat.AMDesignator = "ق.ظ"; DateTimeFormat.PMDesignator = "ب.ظ"; /* DateTimeFormat.ShortDatePattern = "yyyy/MM/dd"; DateTimeFormat.LongDatePattern = "yyyy/MM/dd"; DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd'); DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D'); DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y'); DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y'); */ } public override Calendar Calendar { get { return cal; } } public override Calendar[] OptionalCalendars { get { return optionals; } } } }
using GSD.Globalization; using System.Threading; protected void Application_BeginRequest(object sender, EventArgs e) { var persianCulture = new PersianCulture(); Thread.CurrentThread.CurrentCulture = persianCulture; Thread.CurrentThread.CurrentUICulture = persianCulture; }
private static void seedDb(ApplicationDbContext context) { if (!context.Chapters.Any()) { var user1 = context.Users.Add(new User { Name = "Test User" }); context.Chapters.Add(new Chapter { Title = "Learn SQlite FTS5", Text = "This tutorial teaches you how to perform full-text search in SQLite using FTS5", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Advanced SQlite Full-text Search", Text = "Show you some advanced techniques in SQLite full-text searching", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "SQLite Tutorial", Text = "Help you learn SQLite quickly and effectively", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Handle markup in text", Text = "<p>Isn't this <font face=\"Comic Sans\">funny</font>?", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "آزمایش متن فارسی", Text = "برای نمونه تهیه شدهاست", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Exclude test 1", Text = "in the years 2018-2019 something happened.", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Exclude test 2", Text = "It was 2018 and then it was 2019", User = user1.Entity }); context.SaveChanges(); } }
ثبت اطلاعات فوق، چنین رکوردهایی را در جدول Chapters به وجود میآورد که شامل اطلاعات یونیکد، HTML ای و غیره است:
اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS به صورت مستقیم
کوئریهای Full-text در SQLite، چنین شکل کلی را دارند و توسط تابع match انجام میشوند:
select * from Chapters_FTS where Chapters_FTS match "fts5"
همانطور که مشاهده میکنید در اینجا تنها دو ستونی که ایندکس شدهاند، در خروجی نهایی ظاهر میشوند؛ اما این جدول به همراه ستونهای مخفی توکار دیگری نیز هست:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5"
- Rowid با توجه به تعریفی که در قسمت قبل انجام دادیم:
CREATE VIRTUAL TABLE "Chapters_FTS" USING fts5("Text", "Title", content="Chapters", content_rowid="Id")
- تمام جداول مجازی FTS، به همراه ستون مخفی rank نیز هستند که میزان نزدیک بودن خروجی حاصل را به کوئری درخواستی مشخص میکنند. این عدد توسط تابعی به نام bm25 تهیه میشود. اگر کوئری FTS به همراه قسمت where نباشد، مقدار rank همواره نال خواهد بود. اما اگر قسمت where به همراه match قید شود، مقدار rank، مقدار از پیش محاسبه شدهی تابع توکار bm25 است. به همین جهت کار با این مقدار از پیش محاسبه شده، سریعتر از فراخوانی مستقیم متد bm25 است. برای مثال دو کوئری زیر اساسا یکی هستند؛ اما دومی سریعتر است:
select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY bm25(fts); select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY rank;
یک نکته: کوئری FTS فوق بر روی هر دو ستون title و text اجرا میشود (و یا هر ستون موجود دیگری که پیشتر ایندکس شده باشد).
اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS توسط EF Core
پس از آشنایی مقدماتی با کوئری نویسی FTS در SQLite، بر انجام یک چنین کوئری در EF Core میتوان به صورت زیر عمل کرد:
- ابتدا باید یک موجودیت بدون کلید را مطابق ستونهای مخفی و ایندکس شدهی بازگشتی تهیه کنیم:
namespace EFCoreSQLiteFTS.Entities { public class ChapterFTS { public int RowId { get; set; } public decimal? Rank { get; set; } public string Title { get; set; } public string Text { get; set; } } }
- سپس نیاز است این موجودیت بدون کلید را به EF معرفی کنیم:
namespace EFCoreSQLiteFTS.DataLayer { public class ApplicationDbContext : DbContext { //... protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<ChapterFTS>().HasNoKey().ToView(null); } //... } }
- و در آخر روش کوئری گرفتن از جدول مجازی FTS در EF Core به صورت زیر میباشد که توسط متد FromSqlRaw به صورت پارامتری (مقاوم در برابر حملات تزریق اسکیوال)، قابل انجام است:
const string ftsSql = "SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH {0}"; foreach (var chapter in context.Set<ChapterFTS>().FromSqlRaw(ftsSql, "fts5")) { Console.WriteLine($"Title: {chapter.Title}"); Console.WriteLine($"Text: {chapter.Text}"); }
بررسی قابلیتهای ویژهی کوئریهای FTS در SQLite
اکنون که با روش کلی کوئری گرفتن از جدول مجازی FTS آشنا شدیم، نکات ویژهی آنرا بررسی میکنیم و در اینجا بیشتر پارامتر ذکر شدهی پس از عملگر match تغییر خواهد کرد و مابقی قسمتهای آن ثابت و مانند قبل هستند.
بجای عملگر match میتوان از = نیز استفاده کرد
دو کوئری زیر دقیقا به یک معنا هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5"; SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS = "fts5";
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5" ORDER by rank;
جستجوهایی به همراه واژههایی در کنار هم
از دیدگاه FTS، دو کوئری زیر که در قسمت match آنها، واژهها با فاصله در کنار هم قرار گرفتهاند، یکی هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn SQLite" ORDER by rank; SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn + SQLite" ORDER by rank;
علت اینجا است که یک full-text search بر اساس ایندکس شدن واژهها تولید میشود و هر کدام از این واژهها به یک توکن نگاشت خواهند شد. به همین جهت است که در اینجا تفاوتی بین + و فاصله در عبارت جستجو شده وجود ندارد. در این حالت اگر در یکی از ستونهای ایندکس شده، واژهی learn و یا واژهی SQLite بکار رفته باشد، در خروجی نهایی لیست خواهد شد.
امکان جستجو بر اساس پیشوندها
میتوان با استفاده از *، تمام توکنهای ایندکس شده و شروع شدهی با واژهی مشخصی را جستجو کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search*" ORDER by rank;
امکان استفاده از عملگرهای بولی NOT، AND و OR
اگر learn text را جستجو کنیم:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn text" ORDER by rank;
رکوردی با ID مساوی 1 بازگشت داده میشود. اما اگر نیاز باشد رکوردی بازگشت داده شود که حاوی learn باشد، اما text خیر، میتوان از عملگر NOT استفاده کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn NOT text" ORDER by rank;
که اینبار رکوردی با ID مساوی 3 را بازگشت دادهاست.
نکتهی مهم: عملگرهای بولی FTS مانند AND، OR، NOT و غیره باید با حروف بزرگ قید شوند.
در ادامه مثال دیگری از ترکیب عملگرهای بولی را مشاهده میکنید:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND sqlite OR help" ORDER by rank;
که تقدم و تاخر این عملگرها را میتوان توسط پرانتزها به صورت صریحی نیز مشخص کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND (sqlite OR help)" ORDER by rank;
امکان ذکر صریح ستونهای مدنظر در کوئری
همانطور که عنوان شد، حالت پیشفرض جستجوهای تمام متنی، جستجوی واژهی مدنظر در تمام ستونهای ایندکس شدهاست؛ اما شاید این مورد مدنظر شما نباشد. به همین منظور میتوان ابتدا نام ستون مدنظر را ذکر کرد و پس از آن یک : را قرار داد تا فقط جستجو بر روی آن ستون خاص صورت گیرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "text:some AND title:sqlite" ORDER by rank;
امکان ترکیب نام ستونها به صورت {col2 col1 col3} نیز وجود دارد.
نکتهی مهم! در جستجوهای FTS در SQLite، ذکر - به معنای قید صریح نام یک ستون خاص است (و یا لیست ستونهایی به صورت {col2 col1 col3}-) که قرار نیست چیزی با آن(ها) انطباق داده شود (- شبیه به عملگر NOT عمل میکند؛ اینبار در مورد ستونها) و این مورد عموما تازهکاران را به اشتباه میاندازد. برای مثال در ابتدای بحث، دو رکورد را که دارای text ای مساوی عبارات زیر هستند، ثبت کردیم:
"in the years 2018-2019 something happened" "It was 2018 and then it was 2019"
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "2018-2019" ORDER by rank;
Execution finished with errors. Result: no such column: 2019
و یا میتوان عبارت جستجو شده را بین "" قرار داد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH '"2018-2019"' ORDER by rank;
و یا حتی میتوان '"2018 2019"' را نیز جستجو کرد که نتیجهی مشابهی را ارائه میدهد.
امکان جستجوی بر روی عبارات یونیکد
FTS5 و آخرین نگارش SQLite، به همراه tokenizer مخصوص یونیکد نیز هست و با اینگونه جستجوهای تمام متنی، مشکلی ندارد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "آزمایش" ORDER by rank;
توابع کمکی FTS در SQLite برای متمایز سازی عبارات یافت شدهی در متن
فرض کنید میخواهیم واژهی fts5 را جستجو کرده و همچنین در خروجی نهایی، هرجائیکه fts5 قرار دارد، آنرا به صورت bold نمایش دهیم. برای اینکار، تابع توکار highlight قابل استفادهاست. اما اگر در این بین خواستیم فقط قسمت کوتاهی از متن مورد نظر را که به جستجوی ما نزدیک است نمایش دهیم، میتوان از متد توکار snippet استفاده کرد:
SELECT rowid, highlight(Chapters_FTS, title, '<b>', '</b>') as title, snippet(Chapters_FTS, text, '<b>', '</b>', '...', 64) as text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5" ORDER BY rank
نکتهی مهم: چون بر اساس نکات قسمت قبل، متنی که به Chapters_FTS ارسال میشود، نرمال سازی شدهاست، متدهای فوق کارآیی خودشان را از دست میدهند. برای مثال اگر در کوئری فوق، واژهی funny را که به یک رکورد HTML ای اشاره میکند، جستجو کنیم، خروجی زیر را دریافت خواهیم کرد:
خروجی نهایی، چون به جدول اصلی chapters متصل است، اصل متن را بازگشت میدهد، اما چون اطلاعاتی را که به Chapters_FTS ارسال کردهایم، فاقد تگهای HTML هستند، تا خروجی دقیقی حاصل شود، متدهای highlight و snippet دیگر قادر به علامتگذاری خروجی نهایی نبوده و اینکار را باید خودمان به صورت دستی در سمت کلاینت انجام دهیم.
در این مقاله میخواهیم نحوهٔ ساخت اشیایی با خصوصیات Enumerable را بررسی کنیم. بررسی ویژگی این اشیاء دارای اهمیت است حداقل به این دلیل که پایهٔ یکی از قابلیت مهم زبانی سیشارپ یعنی LINQ هستند. برای یافتن پیشزمینهای در این موضوع خواندن این مقالههای بسیار خوب (۱ و ۲) نیز توصیه میشود.
Enumerableها
اشیاء Enumerable یا بهعبارت دیگر اشیائی که اینترفیس IEnumerable را پیادهسازی میکنند، دامنهٔ گستردهای از Collectionهای CLI را شامل میشوند. همانطور که در نمودار زیر نیز میتوانید مشاهده کنید IEnumerable (از نوع غیر Generic آن) در بالای سلسله مراتب اینترفیسهای Collectionهای CLI قرار دارد:
درخت اینترفیسهای Collectionها در CLI منبع
IEnumerableها همچنین دارای اهمیت دیگری نیز هستند؛ قابلیتهای LINQ که از داتنت ۳.۵ به داتنت اضافه شدند بهعنوان Extensionهای این اینترفیس تعریف شدهاند و پیادهسازی Linq to Objects را میتوانید در کلاس استاتیک System.Linq.Enumerable در System.Core مشاهده کنید. (میتوانید برای دیدن آن را با ILDasm یا Reflector باز کنید یا پیادهسازی آزاد آن در پروژهٔ Mono را اینجا مشاهده کنید که برای شناخت بیشتر LINQ واقعاً مفید است.)
همچنین این Enumerableها هستند که foreach را امکانپذیر میکنند. به عبارتی دیگر هر شئای که قرار باشد در foreach (var x in object) قرار بگیرد و بدین طریق اشیاء درونیاش را برای پیمایش یا عملی خاص قرار دهد باید Enumerable باشد.
همانطور که قبلاً هم اشاره شد IEnumerable از نوع غیر Generic در بالای نمودار Collectionها قرار دارد و حتی IEnumerable از نوع Generic نیز باید آن را پشتیبانی کند. این موضوع به احتمال به این دلیل در طراحی لحاظ شد که مهاجرت به .NET 2.0 که قابلیتهای Generic را افزوده بود سادهتر کند. IEnumerable همچنین قابلیت covariance که از قابلیتهای جدید C# 4.0 هست را دارا است (در اصل IEnumerable دارای Generic از نوع out است).
Enumerableها همانطور که از اسم اینترفیس IEnumerable انتظار میرود اشیایی هستند که میتوانند یک شئ Enumerator که IEnumerator را پیادهسازی کردهاست را از خود ارائه دهند. پس طبیعی است برای فهم و درک دلیل وجودی Enumerable باید Enumerator را بررسی کنیم.
Enumeratorها
Enumerator شئ است که در یک پیمایش یا بهعبارت دیگر گذر از روی تکتک عضوها ایجاد میشود که با حفظ موقعیت فعلی و پیمایش امکان ادامهٔ پیمایش را برای ما فراهم میآورد. اگر بخواهید آن را در حقیقت بازسازی کنید شئ Enumerator بهمانند کاغذ یا جسمی است که بین صفحات یک کتاب قرار میدهید که مکانی که در آن قرار دارید را گم نکنید؛ در این مثال، Enumerable همان کتاب است که قابلیت این را دارد که برای پیمایش به وسیلهٔ قرار دادن یک جسم در وسط آن را دارد.
حال برای اینکه دید بهتری از رابطهٔ بین Enumerable و Enumerator از نظر برنامهنویسی به این موضوع پیدا کنیم یک کد نمونهٔ عملی را بررسی میکنیم.
در اینجا نمونهٔ ساده و خوانایی از استفاده از یک List برای پیشمایش تمامی اعداد قرار دارد:
List<int> list = new List<int>(); list.Add(1); list.Add(2); list.Add(3); foreach (int i in list) { Console.WriteLine(i); }
همانطور که قبلاً اشاره foreach نیاز به یک Enumerable دارد و List هم با پیادهسازی IList که گسترشی از IEnumerable هست نیز یک نوع Enumerable هست. اگر این کد را Compile کنیم و IL آن را بررسی کنیم متوجه میشویم که CLI در اصل چنین کدی را برای اجرا میبینید:
List<int> list = new List<int>(); list.Add(1); list.Add(2); list.Add(3); IEnumerator<int> listIterator = list.GetEnumerator(); while (listIterator.MoveNext()) { Console.WriteLine(listIterator.Current); } listIterator.Dispose();
(میتوان از using استفاده نمود که Dispose را خود انجام دهد که اینجا برای سادگی استفاده نشدهاست.)
همانطور که میبینیم یک Enumerator برای Enumerable ما (یعنی List) ایجاد شد و پس از آن با پرسش این موضوع که آیا این پیمایش امکان ادامه دارد، کل اعضا پیمودهشده و عمل مورد نظر ما بر آنها انجام شدهاست.
خب، تا اینجای کار با خصوصیات و اهمیت Enumeratorها و Enumerableها آشنا شدیم، حال نوبت به آن میرسد که بررسی کنیم آنها را چگونه میسازند و بعد از آن با کاربردهای فراتری از آنها نسبت به پیمایش یک List آشنا شویم.
ساخت Enumeratorها و Enumerableها
همانطور که اشاره شد ایجاد اشیاء Enumerable به اشیاء Enumerator مربوط است، پس ما در یک قطعه کد که پیمایش از روی یک آرایه را فراهم میآورد ایجاد هر دوی آنها و رابطهٔ بینشان را بررسی میکنیم.
public class ArrayEnumerable<T> : IEnumerable<T> { private T[] _array; public ArrayEnumerable(T[] array) { _array = array; } public IEnumerator<T> GetEnumerator() { return new ArrayEnumerator<T>(_array); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } public class ArrayEnumerator<T> : IEnumerator<T> { private T[] _array; public ArrayEnumerator(T[] array) { _array = array; } public int index = -1; public T Current { get { return _array[index]; } } object System.Collections.IEnumerator.Current { get { return this.Current; } } public bool MoveNext() { index++; return index < _array.Length; } public void Reset() { index = 0; } public void Dispose() { } }
خود کتابخانه HtmlAgilityPack به ازای هر HtmlNode ایی که ارائه میدهد، خاصیت XPath معتبری را نیز به همراه دارد. در ادامه قصد داریم از این امکان توکار استفاده کرده و کلیه XPathهای یک محتوای HTML ایی را استخراج کنیم.
پردازش تگهای تو در توی یک HTML به کمک کتابخانه HtmlAgilityPack
using System; using System.Linq; using System.Net; using System.Text; using HtmlAgilityPack; namespace HapTests { public class HtmlReader { public Action<string> ParseError { set; get; } public Func<HtmlNode, bool> ParserHtmlNode { set; get; } public void StartParsingHtml(Uri url) { using (var client = new WebClient { Encoding = Encoding.UTF8 }) { client.Headers.Add("user-agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"); StartParsingHtml(client.DownloadString(url)); } } public void StartParsingHtml(string htmlContent) { if (string.IsNullOrWhiteSpace(htmlContent)) throw new ArgumentNullException("content"); var doc = new HtmlDocument { OptionCheckSyntax = true, OptionFixNestedTags = true, OptionAutoCloseOnEnd = true, OptionDefaultStreamEncoding = Encoding.UTF8 }; doc.LoadHtml(htmlContent); if (doc.ParseErrors != null && doc.ParseErrors.Any()) { foreach (var error in doc.ParseErrors) { if (ParseError != null) ParseError(error.Code + " - " + error.Reason); } } if (!doc.DocumentNode.HasChildNodes) return; handleChildren(doc.DocumentNode.ChildNodes); } private void handleChildren(HtmlNodeCollection nodes) { foreach (var itm in nodes) { if (itm.Name.ToLower().Equals("html")) { if (itm.Element("body") != null) handleChildren(itm.Element("body").ChildNodes); } else handleHtmlNode(itm); } } private void parserChildNodes(HtmlNode content) { foreach (var item in content.ChildNodes) { handleHtmlNode(item); } } private void handleHtmlNode(HtmlNode htmNode) { switch (htmNode.Name.ToLower()) { case "html": case "body": handleChildren(htmNode.ChildNodes); break; default: if (ParserHtmlNode == null) throw new ArgumentNullException("ParserHtmlNode"); if (ParserHtmlNode(htmNode)) parserChildNodes(htmNode); break; } } } }
این کد برای نوشتن مبدلهای HTML به XYZ بسیار مناسب است. برای مثال اگر بخواهید یک مبدل HTML به PDF را تهیه کنید، کدهای ابتدایی آن همین موارد است:
new HtmlReader { ParseError = error => Console.WriteLine(error), ParserHtmlNode = htmlNode => { //switch(htmlNode.Name) { } return true; //it's a nested node. } }.StartParsingHtml(html);
در اینجا html، محتوای HTMLایی در حال بررسی است. ParserHtmlNode یک callback است. هر زمانیکه به یک گره HTML برخورد، آنرا در اختیار شما قرار میدهد. در ادامه فرصت خواهید داشت تا برای نمونه یک swicth را تهیه کرده و مثلا به ازای تگ hr یک خط رسم کنید، به ازای تگ br یک سطر جدید را درنظر بگیرید و الی آخر. اگر خروجی این Func را true درنظر بگیرید، فرض بر این خواهد بود که گره جاری تو در تو است (حالت دنیای واقعی)؛ در غیراینصورت، یک سطح این گره، بیشتر بررسی نخواهد شد.
در این کلاس، ParseError نیز یک callback است و اگر کتابخانه HtmlAgilityPack، در حین آنالیز کدهای HTML دریافتی به خطایی برخورد، آنرا گزارش خواهد داد.
در کلاس فوق، دو حالت برای متد StartParsingHtml در نظر گرفته شده است. در حالت اول، یک Uri یا آدرس اینترنتی دریافت و سپس آنالیز میگردد. در حالت دوم، فرض بر این است که محتوای کدهای HTML مدنظر به هر نحوی پیشتر تهیه شده و به صورت string موجود است.
استخراج کلیه XPathها از یک فایل HTML به کمک کتابخانه HtmlAgilityPack
اکنون که یک HTML Parser عمومی را تهیه کردهایم، استخراج XPathها توسط آن کار سادهای خواهد بود. یک مثال کامل را در این زمینه در ادامه ملاحظه میکنید:
using System; using System.Diagnostics; using System.IO; using System.Text; using HtmlAgilityPack; namespace HapTests { class Program { static void Main(string[] args) { var html = @"<table width='750' border='0' style='font-size: 10pt; width: 736px' class='boxcar2 gerd'> <tbody><tr> <td height='70' colspan='4' class='boxcart1 gerd'> <iframe width='718' scrolling='no'> </iframe></td> </tr> <tr> <td height='70' colspan='4' class='boxcart1 gerd'> </td> </tr> <tr> <td width='193' height='36' class='boxcart2 gerd'> <a target='_self' href='Curr.cbi.2.php'>نرخ ارز مبادله ای بانک مرکزی</a></td> <td width='181' height='36' class='boxcart2 gerd'> <a target='_self' href='Curr.cbi.php'>نرخ ارز مرجع بانک مرکزی</a></td> <td width='149' height='36' class='boxcart2 gerd'> <a target='_self' href='curv.htm'>نمودار قیمت طلا</a></td> <td width='199' height='36' class='boxcart2 gerd'> <a target='_self' href='index.php'>قیمت طلا و سکه در بازار ایران</a></td> </tr> <tr> <td height='48' colspan='4' class='boxcart1 gerd'> <p dir='rtl'><span style='font-size: 13pt;'>تابلو آنلاین قیمت جهانی طلا و نقره ( دلار )</span></p></td> </tr> <tr> <td height='57' colspan='2' class='boxcart1 gerd'>قیمت لحظه ای هر انس نقره در بازارهای جهانی<br> <span style='font-size: 9pt;'> </span></td> <td height='57' colspan='2' class='boxcart1 gerd'>قیمت لحظه ای هر انس طلا در بازارهای جهانی<br> <span style='font-size: 9pt;'> </span></td> </tr> <tr> <td height='48' colspan='4' class='boxcart1 gerd'> <p dir='rtl'><span style='font-size: 13pt'>تابلو آنلاین قیمت طلا ، سکه و نقره در بازار ایران ( ریال )</span></p> </td> </tr> <tr> <td style='direction: rtl; font-size: 8pt' colspan='4'><div align='center'> <table id='gold_tbl'><tbody><tr><th>قیمت طلا</th><th>قیمت زنده</th><th>تغییر</th> <th>کمترین</th><th>بیشترین</th><th>زمان</th></tr><tr><td>انس طلا <sup>دلار</sup></td> <td class='s0_1'>1,375.90</td><td class='c0_1 neg'>(-0.34%) -4.70</td> <td class='l0_1'>1,374.90</td><td class='h0_1'>1,380.80</td><td class='z0_1 fa'>17:53</td> </tr><tr><td>مثقال طلا</td><td class='s3_2'>5,290,000</td> <td class='c3_2 pos'>(1.63%) 85,000</td><td class='l3_2'>5,200,000</td><td class='h3_2'>5,320,000</td><td class='z3_2 fa'>17:50</td></tr><tr><td>گرم طلای 18</td> <td class='s3_3'>1,221,200</td><td class='c3_3 pos'>(1.63%) 19,600</td><td class='l3_3'>1,200,400</td><td class='h3_3'>1,228,100</td><td class='z3_3 fa'>17:50</td> </tr><tr><td>انس نقره <sup>دلار</sup></td><td class='s0_5'>21.83</td><td class='c0_5'>(0.00%) 0.00</td><td class='l0_5'>21.67</td><td class='h0_5'>21.96</td> <td class='z0_5 fa'>17:53</td></tr></tbody></table><br><table id='coin_tbl'><tbody><tr><th>سکه</th><th>قیمت زنده</th><th>تغییر</th><th>کمترین</th> <th>بیشترین</th><th>ارزش طلا</th><th>زمان</th></tr><tr><td>بهار آزادی</td><td class='s3_10'>12,650,000</td><td class='c3_10 pos'>(2.68%) 330,000</td> <td class='l3_10'>12,320,000</td><td class='h3_10'>12,650,000</td><td class='z4_10'>11,918,400</td><td class='z3_10 fa'>16:07</td></tr><tr><td>امامی</td> <td class='s3_11'>12,960,000</td><td class='c3_11 pos'>(2.61%) 330,000</td><td class='l3_11'>12,630,000</td><td class='h3_11'>13,050,000</td><td class='z4_11'>11,918,400</td> <td class='z3_11 fa'>17:43</td></tr><tr><td>نیم</td><td class='s3_12'>6,880,000</td><td class='c3_12 pos'>(2.69%) 180,000</td><td class='l3_12'>6,700,000</td> <td class='h3_12'>6,900,000</td><td class='z4_12'>5,959,200</td><td class='z3_12 fa'>16:08</td></tr><tr><td>ربع</td><td class='s3_13'>4,250,000</td><td class='c3_13 pos'>(2.41%) 100,000</td> <td class='l3_13'>4,150,000</td><td class='h3_13'>4,300,000</td><td class='z4_13'>2,978,100</td><td class='z3_13 fa'>17:42</td></tr><tr><td>گرمی</td><td class='s3_14'>2,940,000</td> <td class='c3_14 pos'>(3.16%) 90,000</td><td class='l3_14'>2,850,000</td><td class='h3_14'>2,940,000</td><td class='z4_14'>1,465,400</td><td class='z3_14 fa'>17:40</td></tr></tbody></table></div></td> </tr> </tbody></table> "; extractXPath(html); test(html); } /// <summary> /// Converts /#comment[1] to /comment()[1] /// or /#text[1] to /text()[1] /// </summary> private static string GetValidXPath(string xpath) { var index = xpath.LastIndexOf("/"); var lastPath = xpath.Substring(index); if (lastPath.Contains("#")) { xpath = xpath.Substring(0, index); lastPath = lastPath.Replace("#", ""); lastPath = lastPath.Replace("[", "()["); xpath = xpath + lastPath; } return xpath; } private static void extractXPath(string html) { var sb = new StringBuilder(); new HtmlReader { ParseError = error => Console.WriteLine(error), ParserHtmlNode = htmlNode => { if (htmlNode is HtmlTextNode) { sb.AppendLine("Text NodeName: " + htmlNode.Name.Trim()); sb.AppendLine("InnerText: " + htmlNode.InnerText.Trim()); } else { sb.AppendLine("NodeName: " + htmlNode.Name.Trim()); var nodeText = new StringBuilder(); for (int i = 0; (i < htmlNode.OuterHtml.Length && htmlNode.OuterHtml[i] != '>'); i++) nodeText.Append(htmlNode.OuterHtml[i]); nodeText.Append(">"); sb.AppendLine("Node Start: " + nodeText.ToString()); } sb.AppendLine("XPath: " + GetValidXPath(htmlNode.XPath.Trim())); sb.AppendLine(Environment.NewLine); return true; //it's a nested node. } }.StartParsingHtml(html); File.WriteAllText("xpath.txt", sb.ToString()); Process.Start("xpath.txt"); } private static void test(string html) { var doc = new HtmlDocument { OptionCheckSyntax = true, OptionFixNestedTags = true, OptionAutoCloseOnEnd = true, OptionDefaultStreamEncoding = Encoding.UTF8 }; doc.LoadHtml(html); var node = doc.DocumentNode.SelectSingleNode("/table[1]/tbody[1]/tr[7]/td[1]/div[1]/table[2]/tbody[1]/tr[6]/td[7]/text()[1]"); Console.WriteLine(node.InnerText); } } }
سپس نمونهای دیگر از نحوه استفاده از کلاس HtmlReader قسمت قبل را در ادامه، در متد extractXPath ملاحظه میکنید. در اینجا کلاس HtmlReader در یک عملیات بازگشتی، کلیه گرههای تو در توی HTML مورد نظر را آنالیز کرده و توسط callback ایی به نام ParserHtmlNode در اختیار ما قرار میدهد. اکنون که این htmlNode را داریم، خاصیت XPath آن دقیقا مقداری است که به دنبالش هستیم.
در اینجا چند نکته حائز اهمیت هستند:
- با بررسی HtmlTextNode، به نودهایی خواهیم رسید که دارای مقدار متنی هستند. در غیراینصورت این گره، خود ابتدای یک سری گره تو در توی دیگر است.
- XPath بازگشتی توسط کتابخانه HtmlAgilityPack نیاز به کمی تمیز سازی دارد. اینکار در متد GetValidXPath انجام شده است.
- در متد test انتهایی، نمونهای از نحوه استفاده از XPathهای استخراجی را ملاحظه میکنید.
Text NodeName: #text InnerText: 17:40 XPath: /table[1]/tbody[1]/tr[7]/td[1]/div[1]/table[2]/tbody[1]/tr[6]/td[7]/text()[1]
استفاده از Fluent Query در دیتا سورس PDFReporter
Cash.Models.ApplicationDbContext db = new Models.ApplicationDbContext(); var listOfRows =db.Users.Join( db.CheckOuts,u=>u.Id,c=>c.ApplicationUserID, (u,c) => new {u.FName,u.Lname,u.NationalCode,c.Creditor,c.Debtor,c.Decsription} ).Where(x => x.Creditor != 0).ToList(); dataSource.AnonymousTypeList(listOfRows);
columns.AddColumn(column => { column.PropertyName<CheckOut>(x => x.Users.FName);
1- تعریف کلاس به صورت زیر
public class ImageSize { public int Height { get; set; } public int Width { get; set; } }
public class ImageResizer { public ImageSize Resize(ImageSize originalSize, ImageSize targetSize) { var aspectRatio = (float)originalSize.Width / (float)originalSize.Height; var width = targetSize.Width; var height = targetSize.Height; if (originalSize.Width > targetSize.Width || originalSize.Height > targetSize.Height) { if (aspectRatio > 1) { height = (int)(targetSize.Height / aspectRatio); } else { width = (int)(targetSize.Width * aspectRatio); } } else { width = originalSize.Width; height = originalSize.Height; } return new ImageSize { Width = Math.Max(width, 1), Height = Math.Max(height, 1) }; } }
3- تعریف کلاس ThumbnailCreator همانند نمونه زیر :
public class ThumbnailCreator { private static readonly IDictionary<string, ImageFormat> ImageFormats = new Dictionary<string, ImageFormat>{ {"image/png", ImageFormat.Png}, {"image/gif", ImageFormat.Gif}, {"image/jpeg", ImageFormat.Jpeg} }; private readonly ImageResizer resizer; public ThumbnailCreator() { this.resizer = new ImageResizer(); } public byte[] Create(Stream source, ImageSize desiredSize, string contentType) { using (var image = Image.FromStream(source)) { var originalSize = new ImageSize { Height = image.Height, Width = image.Width }; var size = resizer.Resize(originalSize, desiredSize); using (var thumbnail = new Bitmap(size.Width, size.Height)) { ScaleImage(image, thumbnail); using (var memoryStream = new MemoryStream()) { thumbnail.Save(memoryStream, ImageFormats[contentType]); return memoryStream.ToArray(); } } } } private void ScaleImage(Image source, Image destination) { using (var graphics = Graphics.FromImage(destination)) { graphics.CompositingMode = CompositingMode.SourceCopy; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.DrawImage(source, 0, 0, destination.Width, destination.Height); } } }
4- تعریف اکشن Thumbnail همانند زیر :
private FileContentResult CreateThumbnail(string physicalPath) { using (var fileStream = System.IO.File.OpenRead(physicalPath)) { var desiredSize = new ImageSize { Width = ThumbnailWidth, Height = ThumbnailHeight }; string contentType = MimeMapping.GetMimeMapping(physicalPath); return File(thumbnailCreator.Create(fileStream, desiredSize, contentType), contentType); } }
و در پایان اکشن GetThumbnail را همانند زیر تغییر خواهیم داد :
public virtual ActionResult GetThumbnail(string path) { path = GetSafeFileAndDirPath(path); // return File(path, contentType); return CreateThumbnail(path); }
در زیر کدی که برای ارسال استفاده نموده ام را قرار دادم.
با تشکر از شما
namespace SchedulerDemo.Jobs { using System; using System.Linq; using System.IO; using Quartz; using System.Collections.Generic; using System.Configuration; [PersistJobDataAfterExecution] [DisallowConcurrentExecution] public class SendJob : IJob { public void Execute(IJobExecutionContext context) { using (var db = new DALModel.DALEntities()) { byte status = (byte)AllEnums.Sms.Status.InProgress; var item = db.SentBoxes.Where(p => p.Status == status && p.IsDeleted==false && p.UserInfo.IsDeleted==false && p.HasTime == true && p.SendInTime == false && p.SendDateX <= DateTime.Now).OrderBy(p=>p.Id).FirstOrDefault(); Cls_SMS.ClsSend sms_Batch = new Cls_SMS.ClsSend(); if (item != null) { decimal smsCount = 0; if (item.UserInfo.CalculateType == Convert.ToByte(AllEnums.FinancialTransaction.CalculationUnit.Message)) { smsCount = Convert.ToDecimal(Function.GetSmsCount(item.Price, item.UserId)); } else { smsCount = Convert.ToDecimal(item.CorrectCount); } decimal adminCredit = Function.GetAdminCreditLink1000(); if (adminCredit != -1 && adminCredit >= smsCount) { if ((item.UserInfo.Credit - (item.UserInfo.LowCredit)) >= item.Price) { item.SendInTime = true; db.SaveChanges(); string numberList = item.NumberList; int position = item.NumberList.LastIndexOf(','); numberList = item.NumberList.Substring(0, position); List<string> receivers_List = new List<string>(); receivers_List = (numberList).Split(',').ToList(); string[] ret2 = new string[2]; string[] DestAdd = new string[receivers_List.Count]; DestAdd = receivers_List.ToArray(); ret2 = sms_Batch.SendSMS_Batch(item.Message, DestAdd, item.UserInfoSenderNumber.AllNumber.Number, ConfigurationManager.AppSettings["SmsUserNameLink1000"], ConfigurationManager.AppSettings["SmsPasswordLink1000"], ConfigurationManager.AppSettings["SmsIPAddressLink1000"], ConfigurationManager.AppSettings["SmsCompanyLink1000"], false, item.Id); var sentBoxUpdate = db.SentBoxes.FirstOrDefault(p => p.Id == item.Id); sentBoxUpdate.Status = Convert.ToByte(AllEnums.Sms.Status.Send); sentBoxUpdate.FinancialTransactionId = db.FinancialTransactions.Where(p => p.UserId == item.UserId).Max(p => p.Id); if (ret2 != null) { sentBoxUpdate.RetValue0 = ret2[0]; sentBoxUpdate.RetValue1 = ret2[1]; } db.SaveChanges(); } else { byte statusFailedForAccount = (byte)AllEnums.Sms.Status.FailedForAccount; item.SendInTime = true; item.Status = statusFailedForAccount; item.FailedCount = item.CorrectCount; item.FailedList = item.NumberList; db.SaveChanges(); } } else { byte statusFaildForError = (byte)AllEnums.Sms.Status.FaildForError; item.SendInTime = true; item.Status = statusFaildForError; item.FailedCount = item.CorrectCount; item.FailedList = item.NumberList; db.SaveChanges(); } } } } } } namespace SchedulerDemo.Interfaces { public interface IScheduleSend { void RunSendSms(); } } namespace SchedulerDemo.Jobs { using System; using Quartz; using Quartz.Impl; using SchedulerDemo.Interfaces; using SchedulerDemo.Jobs; public class SendSchedule : IScheduleSend { public void RunSendSms() { DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second); IJobDetail job = JobBuilder.Create<SendJob>() .WithIdentity("jobSendSmsInTime") .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity("triggerSendSmsInTime") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInMinutes(5).RepeatForever()) .Build(); ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sc = sf.GetScheduler(); sc.ScheduleJob(job, trigger); sc.Start(); } } }
EF Code First #3
public class KalaType { [Key, Column(Order = 0)] public int kalaID { get; set; } [Key, Column(Order = 1)] public int typeID { get; set; } ... }
using System.Data.Entity; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.Design; using System.ComponentModel.DataAnnotations.Resources;
Compiler Error Message: CS0246: The type or namespace name 'Column' could not be found (are you missing a using directive or an assembly reference?)