using PdfRpt.Core.Helper; var data = list.GetSafeStringValueOf("Product.Category.Name"); or var data = list.GetSafeStringValueOf<Entity>(x=>x.Product.Category.Name);
پاسخ به بازخوردهای پروژهها
هر دوی این حالتها کار خواهند کرد:
مطالب
OpenCVSharp #12
قطعه بندی (segmentation) تصویر با استفاده از الگوریتم watershed
در تصویر ذیل، تصویر یک راهرو را مشاهده میکنید که توسط ماوس قطعه بندی شدهاست (تصویر اصلی یا سمت چپ). تصویر سمت راست، نسخهی قطعه بندی شدهی این تصویر به کمک الگوریتم watershed است.
انتخاب نواحی مختلف به کمک ماوس
در اینجا کدهای آغازین مثال بحث جاری را ملاحظه میکنید:
ابتدا تصویر راهرو بارگذاری شدهاست. سپس یک نسخهی سیاه و سفید تک کاناله به نام markerMask از آن استخراج میشود. از آن برای ترسیم خطوط انتخاب نواحی مختلف تصویر به کمک ماوس استفاده میشود. به علاوه متد FindContours که در ادامه معرفی خواهد شد، نیاز به یک تصویر 8 بیتی تک کاناله دارد (به هر یک از اجزای RGB یک کانال گفته میشود).
همچنین این نسخهی سیاه و سفید تک کاناله به یک تصویر سه کاناله برای نمایش رنگهای قسمتهای مختلف قطعه بندی شده، تبدیل میشود.
سپس پنجرهی نمایش تصویر اصلی برنامه ایجاد شده و در اینجا روال رخدادگردان OnMouseCallback آن به صورت inline مقدار دهی شدهاست. در این روال میتوان مدیریت ماوس را به عهده گرفت و کار نمایش خطوط مختلف را با فشرده شدن و سپس رها شدن کلیک سمت چپ ماوس انجام داد.
خط ترسیم شده بر روی دو تصویر از نوع Mat نمایش داده میشود. تصویر srcCopy، همان تصویر نمایش داده شدهی در پنجرهی اصلی است و تصویر markerMask، بیشتر جنبهی محاسباتی دارد و در متدهای بعدی OpenCV استفاده خواهد شد.
تشخیص کانتورها (Contours) در تصویر
پس از ترسیم نواحی مورد نظر توسط ماوس، یک سری خطوط به هم پیوسته در شکل قابل مشاهده هستند. میخواهیم این خطوط را تشخیص داده و سپس از آنها جهت محاسبات قطعه بندی تصویر استفاده کنیم. تشخیص این خطوط متصل، توسط متدی به نام FindContours انجام میشود. کانتورها، قسمتهای خارجی اجزای متصل به هم هستند.
متد FindContours همان تصویر markerMask را که توسط ماوس، قسمتهای مختلف تصویر را علامتگذاری کردهاست، دریافت میکند. سپس کانتورهای آن را استخراج خواهد کرد. کانتورها در مثالهای اصلی OpenCV با verctor مشخص شدهاند. در اینجا (در کتابخانهی OpenCVSharp) آنها را توسط یک آرایهی دو بعدی از نوع Point مشاهده میکنید یا شبیه به لیستی از آرایهی نقاط کانتورهای مختلف تشخیص داده شده (هر کانتور، آرایهی از نقاط است). از hierarchyIndexes جهت یافتن و ترسیم این کانتورها در متد DrawContours استفاده میشود.
متد FindContours یک تصویر 8 بیتی تک کاناله را دریافت میکند. اگر mode آن CCOMP یا FLOODFILL تعریف شود، امکان دریافت یک تصویر 32 بیتی را نیز خواهد داشت.
پارامتر hierarchy آن یک پارامتر اختیاری است که بیانگر اطلاعات topology تصویر است.
توسط پارامتر Mode، نحوهی استخراج کانتور مشخص میشود. اگر به external تنظیم شود، تنها کانتورهای خارجیترین قسمتها را تشخیص میدهد. اگر مساوی list قرار گیرد، تمام کانتورها را بدون ارتباطی با یکدیگر و بدون تشکیل hierarchy استخراج میکند. حالت ccomp تمام کانتورها را استخراج کرده و یک درخت دو سطحی از آنها را تشکیل میدهد. در سطح بالایی مرزهای خارجی اجزاء وجود دارند و در سطح دوم مرزهای حفرهها مشخص شدهاند. حالت و مقدار tree به معنای تشکیل یک درخت کامل از کانتورهای یافت شدهاست.
پارامتر method اگر به none تنظیم شود، تمام نقاط کانتور ذخیره خواهند شد و اگر به simple تنظیم شود، قطعههای افقی، عمودی و قطری، فشرده شده و تنها نقاط نهایی آنها ذخیره میشوند. برای مثال در این حالت یک کانتور مستطیلی، تنها با 4 نقطه ذخیره میشود.
ترسیم کانتورهای تشخیص داده شده بر روی تصویر
میتوان به کمک متد DrawContours، مرزهای کانتورهای یافت شده را ترسیم کرد:
پارامتر اول آن تصویری است که قرار است ترسیمات بر روی آن انجام شوند. پارامتر کانتور، آرایهای است از کانتورهای یافت شدهی در قسمت قبل. پارامتر ایندکس مشخص میکند که اکنون کدام کانتور باید رسم شود. برای یافتن کانتور بعدی باید از hierarchyIndexes یافت شدهی توسط متد FindContours استفاده کرد. خاصیت Next آن، بیانگر ایندکس کانتور بعدی است و اگر مساوی منهای یک شد، کار متوقف میشود. مقدار maxLevel مشخص میکند که بر اساس پارامتر hierarchyIndexes، چند سطح از کانتورهای به هم مرتبط باید ترسیم شوند. در اینجا چون به حداکثر مقدار Int32 تنظیم شدهاست، تمام این سطوح ترسیم خواهند شد. اگر پارامتر ضخامت به یک عدد منفی تنظیم شود، سطوح داخلی کانتور ترسیم و پر میشوند.
اعمال الگوریتم watershed
در مرحلهی آخر، تصویر کانتورهای ترسیم شده را به متد Watershed ارسال میکنیم. پارامتر اول آن تصویر اصلی است و پارامتر دوم، یک پارامتر ورودی و خروجی محسوب میشود و کار قطعه بندی تصویر بر روی آن انجام خواهد شد.
کار الگوریتم watershed، ایزوله سازی اشیاء موجود در تصویر از پس زمینهی آنها است. این الگوریتم، یک تصویر سیاه و سفید را دریافت میکند؛ به همراه یک تصویر ویژه به نام marker. تصویر marker کارش مشخص سازی اشیاء، از پس زمینهی آنها است که در اینجا توسط ماوس ترسیم و سپس به کمک یافتن کانتورها و ترسیم آنها بهینه سازی شدهاست.
متد Cv2.TheRNG یک تولید کنندهی اعداد تصادفی توسط OpenCV است و متد Uniform آن شبیه به متد Next کلاس Random دات نت عمل میکند. به نظر این کلاس تولید اعداد تصادفی، آنچنان هم تصادفی عمل نمیکند. به همین جهت از کلاس Random دات نت استفاده شد. در اینجا به ازای تعداد کانتورهای ترسیم شده، یک رنگ تصادفی تولید شدهاست.
پس از اعمال متد Watershed، هر نقطهی تصویر marker مشخص میکند که متعلق به کدام قطعهی تشخیص داده شدهاست. سپس به این نقطه، رنگ آن قطعه را نسبت داده و آنرا در تصویر جدیدی ترسیم میکنیم.
در آخر، پس زمینه، با نواحی تشخیص داده ترکیب شدهاند (watershedImage * 0.5 + imgGray * 0.5) تا تصویر ابتدای بحث حاصل شود. اگر این ترکیب صورت نگیرد، چنین تصویری حاصل خواهد شد:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
در تصویر ذیل، تصویر یک راهرو را مشاهده میکنید که توسط ماوس قطعه بندی شدهاست (تصویر اصلی یا سمت چپ). تصویر سمت راست، نسخهی قطعه بندی شدهی این تصویر به کمک الگوریتم watershed است.
همانطور که در تصویر نیز مشخص است، نمایش هر ناحیهی قطعه بندی شده، شبیه به سیلان آب است که با رسیدن به مرز قطعهی بعدی متوقف شدهاست. به همین جهت به آن watershed (آب پخشان) میگویند.
انتخاب نواحی مختلف به کمک ماوس
در اینجا کدهای آغازین مثال بحث جاری را ملاحظه میکنید:
var src = new Mat(@"..\..\Images\corridor.jpg", LoadMode.AnyDepth | LoadMode.AnyColor); var srcCopy = new Mat(); src.CopyTo(srcCopy); var markerMask = new Mat(); Cv2.CvtColor(srcCopy, markerMask, ColorConversion.BgrToGray); var imgGray = new Mat(); Cv2.CvtColor(markerMask, imgGray, ColorConversion.GrayToBgr); markerMask = new Mat(markerMask.Size(), markerMask.Type(), s: Scalar.All(0)); var sourceWindow = new Window("Source (Select areas by mouse and then press space)") { Image = srcCopy }; var previousPoint = new Point(-1, -1); sourceWindow.OnMouseCallback += (@event, x, y, flags) => { if (x < 0 || x >= srcCopy.Cols || y < 0 || y >= srcCopy.Rows) { return; } if (@event == MouseEvent.LButtonUp || !flags.HasFlag(MouseEvent.FlagLButton)) { previousPoint = new Point(-1, -1); } else if (@event == MouseEvent.LButtonDown) { previousPoint = new Point(x, y); } else if (@event == MouseEvent.MouseMove && flags.HasFlag(MouseEvent.FlagLButton)) { var pt = new Point(x, y); if (previousPoint.X < 0) { previousPoint = pt; } Cv2.Line(img: markerMask, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5); Cv2.Line(img: srcCopy, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5); previousPoint = pt; sourceWindow.Image = srcCopy; } };
همچنین این نسخهی سیاه و سفید تک کاناله به یک تصویر سه کاناله برای نمایش رنگهای قسمتهای مختلف قطعه بندی شده، تبدیل میشود.
سپس پنجرهی نمایش تصویر اصلی برنامه ایجاد شده و در اینجا روال رخدادگردان OnMouseCallback آن به صورت inline مقدار دهی شدهاست. در این روال میتوان مدیریت ماوس را به عهده گرفت و کار نمایش خطوط مختلف را با فشرده شدن و سپس رها شدن کلیک سمت چپ ماوس انجام داد.
خط ترسیم شده بر روی دو تصویر از نوع Mat نمایش داده میشود. تصویر srcCopy، همان تصویر نمایش داده شدهی در پنجرهی اصلی است و تصویر markerMask، بیشتر جنبهی محاسباتی دارد و در متدهای بعدی OpenCV استفاده خواهد شد.
تشخیص کانتورها (Contours) در تصویر
پس از ترسیم نواحی مورد نظر توسط ماوس، یک سری خطوط به هم پیوسته در شکل قابل مشاهده هستند. میخواهیم این خطوط را تشخیص داده و سپس از آنها جهت محاسبات قطعه بندی تصویر استفاده کنیم. تشخیص این خطوط متصل، توسط متدی به نام FindContours انجام میشود. کانتورها، قسمتهای خارجی اجزای متصل به هم هستند.
Point[][] contours; //vector<vector<Point>> contours; HiearchyIndex[] hierarchyIndexes; //vector<Vec4i> hierarchy; Cv2.FindContours( markerMask, out contours, out hierarchyIndexes, mode: ContourRetrieval.CComp, method: ContourChain.ApproxSimple);
متد FindContours یک تصویر 8 بیتی تک کاناله را دریافت میکند. اگر mode آن CCOMP یا FLOODFILL تعریف شود، امکان دریافت یک تصویر 32 بیتی را نیز خواهد داشت.
پارامتر hierarchy آن یک پارامتر اختیاری است که بیانگر اطلاعات topology تصویر است.
توسط پارامتر Mode، نحوهی استخراج کانتور مشخص میشود. اگر به external تنظیم شود، تنها کانتورهای خارجیترین قسمتها را تشخیص میدهد. اگر مساوی list قرار گیرد، تمام کانتورها را بدون ارتباطی با یکدیگر و بدون تشکیل hierarchy استخراج میکند. حالت ccomp تمام کانتورها را استخراج کرده و یک درخت دو سطحی از آنها را تشکیل میدهد. در سطح بالایی مرزهای خارجی اجزاء وجود دارند و در سطح دوم مرزهای حفرهها مشخص شدهاند. حالت و مقدار tree به معنای تشکیل یک درخت کامل از کانتورهای یافت شدهاست.
پارامتر method اگر به none تنظیم شود، تمام نقاط کانتور ذخیره خواهند شد و اگر به simple تنظیم شود، قطعههای افقی، عمودی و قطری، فشرده شده و تنها نقاط نهایی آنها ذخیره میشوند. برای مثال در این حالت یک کانتور مستطیلی، تنها با 4 نقطه ذخیره میشود.
ترسیم کانتورهای تشخیص داده شده بر روی تصویر
میتوان به کمک متد DrawContours، مرزهای کانتورهای یافت شده را ترسیم کرد:
var markers = new Mat(markerMask.Size(), MatType.CV_32S, s: Scalar.All(0)); var componentCount = 0; var contourIndex = 0; while ((contourIndex >= 0)) { Cv2.DrawContours( markers, contours, contourIndex, color: Scalar.All(componentCount + 1), thickness: -1, lineType: LineType.Link8, hierarchy: hierarchyIndexes, maxLevel: int.MaxValue); componentCount++; contourIndex = hierarchyIndexes[contourIndex].Next; }
اعمال الگوریتم watershed
در مرحلهی آخر، تصویر کانتورهای ترسیم شده را به متد Watershed ارسال میکنیم. پارامتر اول آن تصویر اصلی است و پارامتر دوم، یک پارامتر ورودی و خروجی محسوب میشود و کار قطعه بندی تصویر بر روی آن انجام خواهد شد.
کار الگوریتم watershed، ایزوله سازی اشیاء موجود در تصویر از پس زمینهی آنها است. این الگوریتم، یک تصویر سیاه و سفید را دریافت میکند؛ به همراه یک تصویر ویژه به نام marker. تصویر marker کارش مشخص سازی اشیاء، از پس زمینهی آنها است که در اینجا توسط ماوس ترسیم و سپس به کمک یافتن کانتورها و ترسیم آنها بهینه سازی شدهاست.
var rnd = new Random(); var colorTable = new List<Vec3b>(); for (var i = 0; i < componentCount; i++) { var b = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255); var g = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255); var r = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255); colorTable.Add(new Vec3b((byte)b, (byte)g, (byte)r)); } Cv2.Watershed(src, markers); var watershedImage = new Mat(markers.Size(), MatType.CV_8UC3); // paint the watershed image for (var i = 0; i < markers.Rows; i++) { for (var j = 0; j < markers.Cols; j++) { var idx = markers.At<int>(i, j); if (idx == -1) { watershedImage.Set(i, j, new Vec3b(255, 255, 255)); } else if (idx <= 0 || idx > componentCount) { watershedImage.Set(i, j, new Vec3b(0, 0, 0)); } else { watershedImage.Set(i, j, colorTable[idx - 1]); } } } watershedImage = watershedImage * 0.5 + imgGray * 0.5; Cv2.ImShow("Watershed Transform", watershedImage); Cv2.WaitKey(1); //do events
پس از اعمال متد Watershed، هر نقطهی تصویر marker مشخص میکند که متعلق به کدام قطعهی تشخیص داده شدهاست. سپس به این نقطه، رنگ آن قطعه را نسبت داده و آنرا در تصویر جدیدی ترسیم میکنیم.
در آخر، پس زمینه، با نواحی تشخیص داده ترکیب شدهاند (watershedImage * 0.5 + imgGray * 0.5) تا تصویر ابتدای بحث حاصل شود. اگر این ترکیب صورت نگیرد، چنین تصویری حاصل خواهد شد:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
بازخوردهای پروژهها
عدم نمایش متن در هدر سفارشی
با عرض سلام و خسته نباشید؛ میخوام توی گزارش از هدر سفارشی استفاده کنم و به صورت HTML یه چیزی درست کردم (تستی) اما متن پیغام نشون داده نمیشه.
کدی که استفاده کردم:
و نتیجه ای که میگیرم:
ممنون میشم راهنمایی بفرمایید.
کدی که استفاده کردم:
header.XHtmlHeader(rptHeader => { rptHeader.PageHeaderProperties(new XHeaderBasicProperties { RunDirection = PdfRunDirection.RightToLeft, ShowBorder = true, //ImagesPath = Path.Combine(AppPath.ApplicationPath, "images\\report-logo.png") }); rptHeader.AddPageHeader(pageHeader => { string message = "گزارش تراکنشهای مالی ریز پرداخت ها"; var photo = Path.Combine(AppPath.ApplicationPath, "images\\report-logo.png"); var image = string.Format("<img src='{0}' width='150' />", photo); return string.Format(@"<table style='width:100%;direction:rtl;font-size:15px;'> <tr> <td style='width:20%;'>{2}</td> <td style='width:60%;'>{1}</td> <td style='width:20%;'>{0}</td> </tr> </table>", image, message, DateTime.Now.ToString("yyyy/MM/dd")); }); });
و نتیجه ای که میگیرم:
ممنون میشم راهنمایی بفرمایید.
پاسخ به بازخوردهای پروژهها
تگ a در گزارش
نیازی نیست برای صرفا تبدیل HTML به PDF از کتابخانه PDFReport استفاده کنید. کتابخانه PdfReport برای قسمتهای تبدیل HTML به PDF خودش از HTMLWorker کتابخانه iTextSharp استفاده میکند.
اطلاعات بیشتر
ضمنا این کتابخانه مشکلی با لینکها هم ندارد. یک مثال:
پ.ن.
در هر برنامهای یک گزارش خطا زمان قابل رسیدگی خواهد بود که قابلیت تکرار مجدد داشته باشد به همراه ارائه کامل stack trace خطای دریافتی.
اطلاعات بیشتر
ضمنا این کتابخانه مشکلی با لینکها هم ندارد. یک مثال:
var html = @"<a color='blue' href='https://www.dntips.ir'>سایت دات نت</a>"; using (var pdfDoc = new Document(PageSize.A4)) { PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create)); pdfDoc.Open(); FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf"); StyleSheet styles = new StyleSheet(); styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.FONTFAMILY, "tahoma"); styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.ENCODING, "Identity-H"); styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.ALIGN, HtmlTags.ALIGN_LEFT); var parsedHtmlElements = HTMLWorker.ParseToList(new StringReader(html), styles); PdfPCell pdfCell = new PdfPCell { Border = 0 }; pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_RTL; foreach (var htmlElement in parsedHtmlElements) { pdfCell.AddElement(htmlElement); } var table1 = new PdfPTable(1); table1.WidthPercentage = 100; table1.RunDirection = PdfWriter.RUN_DIRECTION_RTL; table1.AddCell(pdfCell); pdfDoc.Add(table1); }
پ.ن.
در هر برنامهای یک گزارش خطا زمان قابل رسیدگی خواهد بود که قابلیت تکرار مجدد داشته باشد به همراه ارائه کامل stack trace خطای دریافتی.
مطالب
آموزش TypeScript #3
در این پست به تشریح انواع داده در زبان TypeScript و ذکر مثال در این زمینه میپردازیم.
تعریف متغیرها و انواع داده
در TypeScript هنگام تعریف متغیرها باید نوع داده ای آنها را مشخص کنیم. در TypeScript پنج نوع داده ای وجود دارد که در زیر با ذکر مثال تعریف شده اند. مفاهیم ماژول، کلاس و تابع در پست بعدی به تشریح توضیح داده خواهند شد.
number : معادل نوع داده ای number در JavaScript است. برای ذخیره سازی اعداد صحیح و اعشاری استفاده میشود.
یک مثال:
حال باید یک فایل Html برای استفاده از این کلاس داشته باشیم. به صورت زیر:
بعد از اجرای پروزه خروجی به صورت زیر خواهد بود:
string : معادل نوع داده ای رشته ای است و برای ذخیره سازی مجموعه ای از کاراکترها از نوع UTF-16 استفاده میشود.
یک مثال:
کد کامپایل شده و تبدیل آن به JavaScript:
خروجی به صورت زیر است:
boolean: برای ذخیره سازی مقادیر true یا false میباشد.
مثال:
کد کامپایل شده و تبدیل آن به JavaScript:
null: همانند دات نت هنگامی که قصد داشته باشیم مقدار یک متغیر را null اختصاص دهیم از این کلمه کلیدی استفاده میکنیم.
مثال:
کد کامپایل شده و تبدیل آن به JavaScript:
undefined:معادل نوع undefined در Javascript است. اگر به یک متغیر مقدار اختصاص ندهید مقدار آن undefined خواهد بود.
مثال:
کد کامپایل شده و تبدیل آن به JavaScript:
خروجی این مثال نیز به صورت زیر است:
ادامه دارد...
تعریف متغیرها و انواع داده
در TypeScript هنگام تعریف متغیرها باید نوع داده ای آنها را مشخص کنیم. در TypeScript پنج نوع داده ای وجود دارد که در زیر با ذکر مثال تعریف شده اند. مفاهیم ماژول، کلاس و تابع در پست بعدی به تشریح توضیح داده خواهند شد.
number : معادل نوع داده ای number در JavaScript است. برای ذخیره سازی اعداد صحیح و اعشاری استفاده میشود.
یک مثال:
class NumberTypeOfTypeScript { MyFunction() { var p: number; p = 1; var q = 2; var r = 3.33; alert("Value of P=" + p + " Value of q=" + q + " Value of r=" + r); } } window.onload = () =>{ var value = new NumberTypeOfTypeScript(); value.MyFunction(); }
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title>TypeScript HTML App</title> <link rel="stylesheet" href="app.css" type="text/css" /> <script src="app.js"></script> </head> <body> <h1>Number Type in TypeScript</h1> <div id="content"/> </body> </html>
string : معادل نوع داده ای رشته ای است و برای ذخیره سازی مجموعه ای از کاراکترها از نوع UTF-16 استفاده میشود.
یک مثال:
class StringTypeOfTypeScript { Myfunction() { var s: string; s="TypeScript" var empty = ""; var abc = "abc"; alert("Value of s="+ s+" Empty string="+ empty+" Value of abc ="+abc) ; } } window.onload = () =>{ var value = new StringTypeOfTypeScript(); value.Myfunction(); }
var StringTypeOfTypeScript = (function () { function StringTypeOfTypeScript() { } StringTypeOfTypeScript.prototype.Myfunction = function () { var s; s = "TypeScript"; var empty = ""; var abc = "abc"; alert("Value of s=" + s + " Empty string=" + empty + " Value of abc =" + abc); }; return StringTypeOfTypeScript; })(); window.onload = function () { var value = new StringTypeOfTypeScript(); value.Myfunction(); };
boolean: برای ذخیره سازی مقادیر true یا false میباشد.
مثال:
class booleanTypeofTypeScript { MyFunction() { var lie: bool; lie = false; var a = 12; if (typeof (lie) == "boolean" && typeof (a) == "boolean") { alert("Both is boolean type"); } if (typeof (lie) == "boolean" && typeof (a) != "boolean") { alert("lie is boolean type and a is not!") } else { alert("a is boolean type and lie is not!"); } } } window.onload =()=> { var access = new booleanTypeofTypeScript(); access.MyFunction(); }
var booleanTypeofTypeScript = (function () { function booleanTypeofTypeScript() { } booleanTypeofTypeScript.prototype.MyFunction = function () { var lie; lie = false; var a = 12; if(typeof (lie) == "boolean" && typeof (a) == "boolean") { alert("Both is boolean type"); } if(typeof (lie) == "boolean" && typeof (a) != "boolean") { alert("lie is boolean type and a is not!"); } else { alert("a is boolean type and lie is not!"); } }; return booleanTypeofTypeScript; })(); window.onload = function () { var access = new booleanTypeofTypeScript(); access.MyFunction(); };
مثال:
class NullTypeinTypeScript { MyFunction() { var p: number = null; var x = null; if (p== null) { alert("p has null value!"); } else { alert("p has a value"); } } } window.onload = () =>{ var value = new NullTypeinTypeScript(); value.MyFunction(); }
var NullTypeinTypeScript = (function () { function NullTypeinTypeScript() { } NullTypeinTypeScript.prototype.MyFunction = function () { var p = null; var x = null; if(p == null) { alert("p has null value!"); } else { alert("p has a value"); } }; return NullTypeinTypeScript; })(); window.onload = function () { var value = new NullTypeinTypeScript(); value.MyFunction(); };
undefined:معادل نوع undefined در Javascript است. اگر به یک متغیر مقدار اختصاص ندهید مقدار آن undefined خواهد بود.
مثال:
class UndefinedTypeOfTypeScript { Myfunction() { var p: number; var x = undefined; if (p == undefined && x == undefined) { alert("p and x is undefined"); } else { alert("p and c cannot undefined"); } } } window.onload = () =>{ var value = new UndefinedTypeOfTypeScript(); value.Myfunction(); }
var UndefinedTypeOfTypeScript = (function () { function UndefinedTypeOfTypeScript() { } UndefinedTypeOfTypeScript.prototype.Myfunction = function () { var p; var x = undefined; if(p == undefined && x == undefined) { alert("p and x is undefined"); } else { alert("p and c cannot undefined"); } }; return UndefinedTypeOfTypeScript; })(); window.onload = function () { var value = new UndefinedTypeOfTypeScript(); value.Myfunction(); };
ادامه دارد...
قبلا مطالبی در سایت راجع به نوع داده شمارشی یا Enum و همچنین CheckBoxList و RadioButtonList وجود دارد. اما در این مطلب قصد دارم تا یک روش متفاوت را برای تولید و بهره گیری از CheckBoxList با استفاده از نوع دادههای شمارشی برای شما ارائه کنم.
فرض کنید بخواهید به کاربر این امکان را بدهید تا بتواند چندین گزینه را برای یک فیلد انتخاب کند. به عنوان یک مثال ساده فرض کنید گزینه ای از مدل، پارچههای مورد علاقه یک نفر هست. کاربر میتواند چندین پارچه را انتخاب کند. و این فرض را هم بکنید که به لیست پارچهها گزینه دیگری اضافه نخواهد شد. پارچه (Fabric) را مثلا میتوانیم به صورت زیر تقسیم بندی کنیم :
حال فرض کنید View Model زیر فیلدی از نوع نوع داده شمارشی Fabric دارد:
توجه داشته باشید که فیلد Fabric از کلاس MyViewModel باید چند مقدار را در خود نگهداری کند. یعنی میتواند هر کدام از گزینههای Cotton، Silk، Wool، Rayon، Other به صورت جداگانه یا ترکیبی باشد. اما در حال حاضر با توجه به اینکه یک فیلد Enum معمولی فقط میتواند یک مقدار را در خودش ذخیره کند قابلیت ذخیره ترکیبی مقادیر در فیلد Fabric از View Model بالا وجود ندارد.
اما راه حل این مشکل استفاده از پرچم (Flags) در تعریف نوع داده شمارشی هست. با استفاده از پرچم نوع داده شمارشی بالا به صورت زیر باید تعریف شود:
همان طور که میبینید از عبارت [Flags] قبل از تعریف enum استفاده کرده ایم. همچنین هر کدام از مقادیر ممکن این نوع داده شمارشی با توانهایی از 2 تنظیم شده اند. در این صورت یک نمونه از این نوع داده میتواند چندین مقدار را در خودش ذخیره کند.
برای آشنایی بیشتر با این موضوع به کدهای زیر نگاه کنید:
به وسیله عملگر | میتوان چندین مقدار را در یک نمونه از نوع Fabric ذخیره کرد. مثلا متغیر cotWool هم دارای مقدار Fabric.Cotton و هم دارای مقدار Fabric.Wool هست. مقدار عددی معادل متغیر cotWool برابر 5 هست که از جمع مقدار عددی Fabric.Cotton و Fabric.Wool به دست آمده است.
حال فرض کنید فیلد Fabric از View Model ذکر شده (کلاس MyViewModel) را به صورت لیستی از چک باکسها نمایش دهیم. مثل زیر:
برای این کار از پروژه MVC Enum Flags کمک خواهیم گرفت. این پروژه شامل یک Html Helper برای تبدیل یه Enum به یک CheckBoxList و همچنین شامل Model Binder مربوطه هست. البته بعضی از کدهای Html Helper آن احتیاج به تغییر داشت که آنرا انجام دادم ولی بایندر آن بسیار خوب کار میکند.
خوب html helper مربوط به آن به صورت زیر میباشد:
در کدهای بالا از متد الحاقی ToDescription نیز برای تبدیل معادل انگلیسی به فارسی یک مقدار از نوع داده شمارشی استفاده کرده ایم.
برای استفاده از این Html Helper در View کد زیر را مینویسیم:
که باعث تولید خروجی که در تصویر (الف) نشان داده شد میشود. و همچنین مدل بایندر مربوط به آن به صورت زیر هست:
این مدل بایندر را باید به این صورت در متد Application_Start فایل Global.asax فراخوانی کنیم:
مشاهده میکنید که در اینجا دقیقا مشخص کرده ایم که این مدل بایندر برای نوع داده شمارشی Fabric هست. اگر نیاز دارید تا این بایندر برای نوع دادههای شمارشی دیگری نیز به کار رود نیاز هست تا این خط کد را برای هر کدام از آنها تکرار کنید. اما راه حل بهتر این هست که کلاسی به صورت زیر تعریف کنیم و تمامی نوع دادههای شمارشی که باید از بایندر بالا استفاده کنند را در یک پراپرتی آن برگشت دهیم. مثلا بدین صورت:
سپس به متد Application_Start رفته و کد زیر را اضافه میکنیم:
اگر گزینههای پشم و ابریشم مصنوعی را از CheckBoxList تولید شده انتخاب کنیم، بدین صورت:
و سپس فرم را پست کنید، موردی شبیه زیر مشاهده میکنید:
همچنین مقدار عددی معادل در این جا برابر 12 میباشد که از جمع دو مقدار Wool و Rayon به دست آمده است. بدین ترتیب در یک فیلد از مدل، گزینههای انتخابی توسط کاربر قرار گرفته شده اند.
پروژه مربوط به این مثال را از لینک زیر دریافت کنید:
MvcEnumFlagsProjectSample.zip
پی نوشت : پوشههای bin و obj و packages جهت کاهش حجم پروژه از آن حذف شده اند. برای بازسازی پوشه packages لطفا به مطلب بازسازی کامل پوشه packages بستههای NuGet به صورت خودکار مراجعه کنید.
فرض کنید بخواهید به کاربر این امکان را بدهید تا بتواند چندین گزینه را برای یک فیلد انتخاب کند. به عنوان یک مثال ساده فرض کنید گزینه ای از مدل، پارچههای مورد علاقه یک نفر هست. کاربر میتواند چندین پارچه را انتخاب کند. و این فرض را هم بکنید که به لیست پارچهها گزینه دیگری اضافه نخواهد شد. پارچه (Fabric) را مثلا میتوانیم به صورت زیر تقسیم بندی کنیم :
- پنبه (Cotton)
- ابریشم (Silk)
- پشم (Wool)
- ابریشم مصنوعی (Rayon)
- پارچههای دیگر (Other)
با توجه به اینکه دیگر قرار نیست به این لیست گزینه دیگری اضافه شود میتوانیم آنرا به صورت یک نوع داده شمارشی (Enum) تعریف کنیم. مثلا بدین صورت:
public enum Fabric { [Description("پنبه")] Cotton, [Description("ابریشم")] Silk, [Description("پشم")] Wool, [Description("ابریشم مصنوعی")] Rayon, [Description("پارچههای دیگر")] Other }
حال فرض کنید View Model زیر فیلدی از نوع نوع داده شمارشی Fabric دارد:
public class MyViewModel { public Fabric Fabric { get; set; } }
توجه داشته باشید که فیلد Fabric از کلاس MyViewModel باید چند مقدار را در خود نگهداری کند. یعنی میتواند هر کدام از گزینههای Cotton، Silk، Wool، Rayon، Other به صورت جداگانه یا ترکیبی باشد. اما در حال حاضر با توجه به اینکه یک فیلد Enum معمولی فقط میتواند یک مقدار را در خودش ذخیره کند قابلیت ذخیره ترکیبی مقادیر در فیلد Fabric از View Model بالا وجود ندارد.
اما راه حل این مشکل استفاده از پرچم (Flags) در تعریف نوع داده شمارشی هست. با استفاده از پرچم نوع داده شمارشی بالا به صورت زیر باید تعریف شود:
[Flags] public enum Fabric { [Description("پنبه")] Cotton = 1, [Description("ابریشم")] Silk = 2, [Description("پشم")] Wool = 4, [Description("ابریشم مصنوعی")] Rayon = 8, [Description("پارچههای دیگر")] Other = 128 }
برای آشنایی بیشتر با این موضوع به کدهای زیر نگاه کنید:
Fabric cotWool = Fabric.Cotton | Fabric.Wool; int cotWoolValue = (int) cotWool;
حال فرض کنید فیلد Fabric از View Model ذکر شده (کلاس MyViewModel) را به صورت لیستی از چک باکسها نمایش دهیم. مثل زیر:
شکل (الف)
سپس بخواهیم تا کاربر بعد از انتخاب گزینههای مورد نظرش از لیست بالا و پست کردن فرم مورد نظر، بایندر وارد عمل شده و فیلد Fabric را بر اساس گزینه هایی که کاربر انتخاب کرده مقداردهی کند. برای این کار از پروژه MVC Enum Flags کمک خواهیم گرفت. این پروژه شامل یک Html Helper برای تبدیل یه Enum به یک CheckBoxList و همچنین شامل Model Binder مربوطه هست. البته بعضی از کدهای Html Helper آن احتیاج به تغییر داشت که آنرا انجام دادم ولی بایندر آن بسیار خوب کار میکند.
خوب html helper مربوط به آن به صورت زیر میباشد:
public static IHtmlString CheckBoxesForEnumFlagsFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression) { ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); Type enumModelType = metadata.ModelType; // Check to make sure this is an enum. if (!enumModelType.IsEnum) { throw new ArgumentException("This helper can only be used with enums. Type used was: " + enumModelType.FullName.ToString() + "."); } // Create string for Element. var sb = new StringBuilder(); foreach (Enum item in Enum.GetValues(enumModelType)) { if (Convert.ToInt32(item) != 0) { var ti = htmlHelper.ViewData.TemplateInfo; var id = ti.GetFullHtmlFieldId(item.ToString()); //Derive property name for checkbox name var body = expression.Body as MemberExpression; var propertyName = body.Member.Name; var name = ti.GetFullHtmlFieldName(propertyName); //Get currently select values from the ViewData model TEnum selectedValues = expression.Compile().Invoke(htmlHelper.ViewData.Model); var label = new TagBuilder("label"); label.Attributes["for"] = id; label.Attributes["style"] = "display: inline-block;"; var field = item.GetType().GetField(item.ToString()); // Add checkbox. var checkbox = new TagBuilder("input"); checkbox.Attributes["id"] = id; checkbox.Attributes["name"] = name; checkbox.Attributes["type"] = "checkbox"; checkbox.Attributes["value"] = item.ToString(); if ((selectedValues as Enum != null) && ((selectedValues as Enum).HasFlag(item))) { checkbox.Attributes["checked"] = "checked"; } sb.AppendLine(checkbox.ToString()); // Check to see if DisplayName attribute has been set for item. var displayName = field.GetCustomAttributes(typeof(DisplayNameAttribute), true) .FirstOrDefault() as DisplayNameAttribute; if (displayName != null) { // Display name specified. Use it. label.SetInnerText(displayName.DisplayName); } else { // Check to see if Display attribute has been set for item. var display = field.GetCustomAttributes(typeof(DisplayAttribute), true) .FirstOrDefault() as DisplayAttribute; if (display != null) { label.SetInnerText(display.Name); } else { label.SetInnerText(item.ToDescription()); } } sb.AppendLine(label.ToString()); // Add line break. sb.AppendLine("<br />"); } } return new HtmlString(sb.ToString()); }
public static string ToDescription(this Enum value) { var attributes = (DescriptionAttribute[])value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); return attributes.Length > 0 ? attributes[0].Description : value.ToString(); }
@Html.CheckBoxesForEnumFlagsFor(x => x.Fabric)
که باعث تولید خروجی که در تصویر (الف) نشان داده شد میشود. و همچنین مدل بایندر مربوط به آن به صورت زیر هست:
public class FlagEnumerationModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException("bindingContext"); if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { var values = GetValue<string[]>(bindingContext, bindingContext.ModelName); if (values.Length > 1 && (bindingContext.ModelType.IsEnum && bindingContext.ModelType.IsDefined(typeof(FlagsAttribute), false))) { long byteValue = 0; foreach (var value in values.Where(v => Enum.IsDefined(bindingContext.ModelType, v))) { byteValue |= (int)Enum.Parse(bindingContext.ModelType, value); } return Enum.Parse(bindingContext.ModelType, byteValue.ToString()); } else { return base.BindModel(controllerContext, bindingContext); } } return base.BindModel(controllerContext, bindingContext); } private static T GetValue<T>(ModelBindingContext bindingContext, string key) { if (bindingContext.ValueProvider.ContainsPrefix(key)) { ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(key); if (valueResult != null) { bindingContext.ModelState.SetModelValue(key, valueResult); return (T)valueResult.ConvertTo(typeof(T)); } } return default(T); } }
این مدل بایندر را باید به این صورت در متد Application_Start فایل Global.asax فراخوانی کنیم:
ModelBinders.Binders.Add(typeof(Fabric), new FlagEnumerationModelBinder());
مشاهده میکنید که در اینجا دقیقا مشخص کرده ایم که این مدل بایندر برای نوع داده شمارشی Fabric هست. اگر نیاز دارید تا این بایندر برای نوع دادههای شمارشی دیگری نیز به کار رود نیاز هست تا این خط کد را برای هر کدام از آنها تکرار کنید. اما راه حل بهتر این هست که کلاسی به صورت زیر تعریف کنیم و تمامی نوع دادههای شمارشی که باید از بایندر بالا استفاده کنند را در یک پراپرتی آن برگشت دهیم. مثلا بدین صورت:
public class ModelEnums { public static IEnumerable<Type> Types { get { var types = new List<Type> { typeof(Fabric) }; return types; } } }
سپس به متد Application_Start رفته و کد زیر را اضافه میکنیم:
foreach (var type in ModelEnums.Types) { ModelBinders.Binders.Add(type, new FlagEnumerationModelBinder()) }
اگر گزینههای پشم و ابریشم مصنوعی را از CheckBoxList تولید شده انتخاب کنیم، بدین صورت:
شکل (ب)
و سپس فرم را پست کنید، موردی شبیه زیر مشاهده میکنید:
شکل (ج)
همچنین مقدار عددی معادل در این جا برابر 12 میباشد که از جمع دو مقدار Wool و Rayon به دست آمده است. بدین ترتیب در یک فیلد از مدل، گزینههای انتخابی توسط کاربر قرار گرفته شده اند.
پروژه مربوط به این مثال را از لینک زیر دریافت کنید:
MvcEnumFlagsProjectSample.zip
پی نوشت : پوشههای bin و obj و packages جهت کاهش حجم پروژه از آن حذف شده اند. برای بازسازی پوشه packages لطفا به مطلب بازسازی کامل پوشه packages بستههای NuGet به صورت خودکار مراجعه کنید.
نظرات مطالب
EF Code First #13
باید از Dynamic LINQ استفاده کنید.
پروژهها
فروشگاه IrisStore
پروژه IrisStore، یک سیستم فروشگاهی متن باز برای راه اندازی فروشگاههای اینترنتی کوچک است که سورس آن را میتوانید از آدرس زیر دریافت کنید و برای اجرای آن نیاز به VS 2015 دارید:
https://github.com/MehdiSaeedifar/IrisStore
همچنین نمونهی آنلاین آنرا میتوانید در فروشگاه آیریس مشاهده کنید.
در ادامه برخی از قابلیتهای این سیستم را مشاهده میکنید:
جست و جو با قابلیت دسته بندی نتایج
به هنگام جست و جو، لیستی از موارد پیشنهادی به صورت دسته بندی شده نمایش داده میشود.
جست و جوی پیشرفته کالاها
جست و جو بر اساس قیمت، گروه، کلمات کلیدی و مرتب سازی نتایج انجام میگیرد. همچنین نتایج جست و جو بدون رفرش شدن صفحه و به صورت AJAX ای به همراه تغییر URL صفحه صورت میگیرد.
نمایش نمودار تغییرات قیمت
امکان نمایش نمودار تغییرات قیمت کالا در بازهی زمانی نیز پیش بینی شده است.
ویرایش اطلاعات به صورت inline
امکان ویرایش قیمت و تاریخ به صورت inline وجود دارد.
همچنین به راحتی میتوان با طراحی قالب جدیدی، از این سیستم برای کاری غیر از فروشگاه اینترنتی استفاده کرد؛ سایتهای زیر نمونههای آنلاین دیگری از این سیستم هستند:
- http://www.petrapars.ir
- http://www.ava-tarh.ir
در نهایت فهرستی از کتاب خانهها و فناوریهای استفاده شده و همچنین مقالات مرتبط با این پروژه را قرار دادهام.
کتابخانهها و فریم ورکهای سمت سرور:
https://github.com/MehdiSaeedifar/IrisStore
همچنین نمونهی آنلاین آنرا میتوانید در فروشگاه آیریس مشاهده کنید.
در ادامه برخی از قابلیتهای این سیستم را مشاهده میکنید:
جست و جو با قابلیت دسته بندی نتایج
به هنگام جست و جو، لیستی از موارد پیشنهادی به صورت دسته بندی شده نمایش داده میشود.
جست و جوی پیشرفته کالاها
جست و جو بر اساس قیمت، گروه، کلمات کلیدی و مرتب سازی نتایج انجام میگیرد. همچنین نتایج جست و جو بدون رفرش شدن صفحه و به صورت AJAX ای به همراه تغییر URL صفحه صورت میگیرد.
نمایش نمودار تغییرات قیمت
امکان نمایش نمودار تغییرات قیمت کالا در بازهی زمانی نیز پیش بینی شده است.
ویرایش اطلاعات به صورت inline
امکان ویرایش قیمت و تاریخ به صورت inline وجود دارد.
مدیریت تصاویر کالا
در این قسمت امکان آپلود همزمان چندین فایل به همراه پیش نمایش آنها وجود دارد. همچنین امکان کشیدن و رها کردن برای تغییر ترتیب چیدمان عکسها نیز مهیا است.( تصویر اول به عنوان کاور کالا در نظر گرفته میشود.)
قابلیتهای دیگر:
- مدیریت تصاویر اسلایدشو و تغییر ترتیب آنها از طریق کشیدن و رها کردن (drag & drop)
- تعریف برگه و تغییر ترتیب نمایش آنها از طریق کشیدن و رها کردن
- امکان ارسال پست
- تعریف دسته بندی
- مدیریت کاربران
- تعریف تنظیمات سایت
- نمایش کالا و پستهای مشابه
تصویر پنل مدیریت
تصویر صفحهی اصلی:
همچنین به راحتی میتوان با طراحی قالب جدیدی، از این سیستم برای کاری غیر از فروشگاه اینترنتی استفاده کرد؛ سایتهای زیر نمونههای آنلاین دیگری از این سیستم هستند:
- http://www.petrapars.ir
- http://www.ava-tarh.ir
در نهایت فهرستی از کتاب خانهها و فناوریهای استفاده شده و همچنین مقالات مرتبط با این پروژه را قرار دادهام.
کتابخانهها و فریم ورکهای سمت سرور:
کتابخانههای جاوا اسکریپتی سمت کلاینت:
فریمورکهای CSS:
فناوری یا کتابخانه | توضیحات | مقالات مرتبط |
Bootstrap 3.x | فریم ورک پایه ای css سایت | - Bootstrap 3 RTL Theme - Twitter Bootstrap -سازگارسازی کلاسهای اعتبارسنجی Twitter Bootstrap 3 با فرمهای ASP.NET MVC -ساخت قالبهای نمایشی و ادیتور دکمه سه وضعیتی سازگار با Twitter bootstrap در ASP.NET MVC -نمایش اخطارها و پیامهای بوت استرپ به کمک TempData در ASP.NET MVC |
AdminLTE | قالب مدیریت سایت | - نسخه راستچین شده AdminLTE 2.2.1 |
Animate.css | انیمیشنهای css3 سایت | |
Font Awesome | پک آیکونهای برداری | |
Awesome Bootstrap Checkbox | زیبا سازی چک باکس ها | |
فونت فارسی وزیر | قلم فارسی | |
گوگل خلاصه نتایج Indexing یک سایت را توسط ابزاری به نام Google webmaster tools در اختیار علاقمندان قرار میدهد. Bing نیز چنین ابزاری را تدارک دیده است.
به آمارهای خطای حاصل از سایت جاری که دقت میکردم یک نکته آن جالب بود: «محتوای تکراری»
همانطور که ملاحظه میکنید، گوگل به کوچکی و بزرگی حروف بکار رفته در لینکها حساس است. هرچند 4 لینک فوق به یک صفحه اشاره میکنند، اما گوگل 4 بار آنها را ایندکس خواهد کرد و نهایتا به صورت یک خطای «محتوای تکراری» در گزارشات SEO آن ظاهر خواهد شد (به همراه کاهش رتبه SEO سایت).
راه حل
برای حل این مساله دو نکته باید درنظر گرفته شود:
الف) هدایت دائمی (Redirect permanent) صفحات قدیمی به صفحاتی جدید، با آدرس lowercase
کلاس فوق، نگارش تکمیل شده ForceWww که پیشتر در این سایت دیدهاید. توسط آن سه بررسی مختلف بر روی لینک جاری در حال پردازش صورت خواهد گرفت:
- تمام آدرسهای سایت باید www داشته باشند؛ تا آدرسهای آن یکنواخت شده و خصوصا مشکلات لاگین و نوشته شدن کوکیها به ازای آدرسهای مختلف و سر درگمی کاربران کاهش یابد.
- اگر آدرس جاری lowercase نباشد، تبدیل به نمونه lowercase شده و درخواست کننده، به آدرس جدید هدایت میشود. این مورد خصوصا جهت موتورهای جستجو برای تصحیح نتایج آنها بسیار مفید است.
- اسلش انتهای لینکها در صورت وجود حذف خواهد شد. این مورد نیز در کاهش تعداد خطاهای «محتوای تکراری» مؤثر است.
- اگر آدرسی، کوئری استرینگ داشته باشد از آن صرفنظر خواهد شد؛ زیرا ممکن است اطلاعات موجود در آن به کوچکی و بزرگی حروف حساس باشند.
ب) کاهش بار سایت توسط تولید خودکار Urlهایی که در بدو امر lowercase هستند
برای پیاده سازی این مطلب میتوان از پروژه سورس باز «LowercaseRoutesMVC» استفاده کرد. سه فایل cs دارد که میتوانید به پروژه خود اضافه کنید. پس از آن، هرجایی در پروژه خود routes.MapRoute دارید تبدیل کنید به routes.MapRouteLowercase .
به این ترتیب به صورت خودکار تمام Urlهای تولید شده توسط HTML helpers توکار ASP.NET MVC (و نه Urlهایی که دستی نوشته شدهاند)، در حین درج در صفحه به صورت lowercase ظاهر خواهند شد (صرفنظر از اینکه نامهای کنترلرها و یا اکشن متدهای تعریف شده camel case هستند یا خیر). مزیت این مساله کاهش یک مرحله Redirect است که در قسمت الف ذکر شد. در این کتابخانه کمکی نیز از آدرسهایی که دارای کوئری استرینگ باشند، صرفنظر میشود.
به آمارهای خطای حاصل از سایت جاری که دقت میکردم یک نکته آن جالب بود: «محتوای تکراری»
mydomain.com/Home/Index mydomain.com/home/index mydomain.com/Home/index mydomain.com/home/Index
راه حل
برای حل این مساله دو نکته باید درنظر گرفته شود:
الف) هدایت دائمی (Redirect permanent) صفحات قدیمی به صفحاتی جدید، با آدرس lowercase
using System.Globalization; using System.Web; using System.Web.Mvc; namespace WebToolkit { public class ForceWww : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { modifyUrlAndRedirectPermanent(filterContext); base.OnActionExecuting(filterContext); } private static void modifyUrlAndRedirectPermanent(ActionExecutingContext filterContext) { if (canIgnoreRequest(filterContext)) return; var absoluteUrl = HttpUtility.UrlDecode(filterContext.RequestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture)); var absoluteUrlToLower = absoluteUrl.ToLowerInvariant(); absoluteUrlToLower = forceWwwAndLowercase(filterContext, absoluteUrlToLower); absoluteUrlToLower = avoidTrailingSlashes(filterContext, absoluteUrlToLower); if (!absoluteUrl.Equals(absoluteUrlToLower)) { filterContext.Result = new RedirectResult(absoluteUrlToLower, permanent: true); } } private static string avoidTrailingSlashes(ActionExecutingContext filterContext, string absoluteUrlToLower) { if (!isRootRequest(filterContext) && absoluteUrlToLower.EndsWith("/")) return absoluteUrlToLower.TrimEnd(new[] { '/' }); return absoluteUrlToLower; } private static bool isRootRequest(ActionExecutingContext filterContext) { return filterContext.RequestContext.HttpContext.Request.Url.AbsolutePath == "/"; } private static bool canIgnoreRequest(ActionExecutingContext filterContext) { return filterContext.IsChildAction || filterContext.HttpContext.Request.IsAjaxRequest() || filterContext.RequestContext.HttpContext.Request.Url.AbsoluteUri.Contains("?"); } private static string forceWwwAndLowercase(ActionExecutingContext filterContext, string absoluteUrlToLower) { if (isLocalRequet(filterContext)) return absoluteUrlToLower; if (absoluteUrlToLower.Contains("www")) return absoluteUrlToLower; return absoluteUrlToLower.Replace("http://", "http://www.") .Replace("https://", "https://www."); } private static bool isLocalRequet(ActionExecutingContext filterContext) { return filterContext.RequestContext.HttpContext.Request.IsLocal; } } }
- تمام آدرسهای سایت باید www داشته باشند؛ تا آدرسهای آن یکنواخت شده و خصوصا مشکلات لاگین و نوشته شدن کوکیها به ازای آدرسهای مختلف و سر درگمی کاربران کاهش یابد.
- اگر آدرس جاری lowercase نباشد، تبدیل به نمونه lowercase شده و درخواست کننده، به آدرس جدید هدایت میشود. این مورد خصوصا جهت موتورهای جستجو برای تصحیح نتایج آنها بسیار مفید است.
- اسلش انتهای لینکها در صورت وجود حذف خواهد شد. این مورد نیز در کاهش تعداد خطاهای «محتوای تکراری» مؤثر است.
- اگر آدرسی، کوئری استرینگ داشته باشد از آن صرفنظر خواهد شد؛ زیرا ممکن است اطلاعات موجود در آن به کوچکی و بزرگی حروف حساس باشند.
ب) کاهش بار سایت توسط تولید خودکار Urlهایی که در بدو امر lowercase هستند
برای پیاده سازی این مطلب میتوان از پروژه سورس باز «LowercaseRoutesMVC» استفاده کرد. سه فایل cs دارد که میتوانید به پروژه خود اضافه کنید. پس از آن، هرجایی در پروژه خود routes.MapRoute دارید تبدیل کنید به routes.MapRouteLowercase .
به این ترتیب به صورت خودکار تمام Urlهای تولید شده توسط HTML helpers توکار ASP.NET MVC (و نه Urlهایی که دستی نوشته شدهاند)، در حین درج در صفحه به صورت lowercase ظاهر خواهند شد (صرفنظر از اینکه نامهای کنترلرها و یا اکشن متدهای تعریف شده camel case هستند یا خیر). مزیت این مساله کاهش یک مرحله Redirect است که در قسمت الف ذکر شد. در این کتابخانه کمکی نیز از آدرسهایی که دارای کوئری استرینگ باشند، صرفنظر میشود.