اشتراکها
اشتراکها
بررسی ()Object.observe در HTML 5
پیشنیاز
نحوه ذخیره شدن متن در فایلهای PDF
حتما نیاز است پیشنیاز فوق را یکبار مطالعه کنید تا علت خروجیهای متفاوتی را که در ادامه ملاحظه خواهید نمود، بهتر مشخص شوند. همچنین فایل PDF ایی که مورد بررسی قرار خواهد گرفت، همان فایلی است که توسط متد writePdf ذکر شده در پیشنیاز تهیه شده است.
دو کلاس متفاوت برای استخراج متن از فایلهای PDF در iTextSharp وجود دارند:
الف) SimpleTextExtractionStrategy
مثال فوق، متن موجود در تمام صفحات یک فایل PDF را در فایلهای txt جداگانهای ثبت میکند. برای نمونه اگر از PDF پیشنیاز یاد شده استفاده کنیم، خروجی آن به نحو زیر خواهد بود:
علت آن نیز پیشتر بررسی گردید. متن، در این فایل ویژه در مختصات خاصی ترسیم شده است. حاصل از دیدگاه خواننده نهایی بسیار خوانا است؛ اما خروجی hello world متنی جالبی از آن استخراج نمیشود. SimpleTextExtractionStrategy دقیقا بر اساس همان عملگرهای Tj و همچنین منابع صفحه، عبارات را یافته و سر هم میکند.
ب) LocationTextExtractionStrategy
همان مثال قبل را درنظر بگیرید، اینبار به شکل زیر:
کلاس LocationTextExtractionStrategy هوشمندتر عمل کرده و بر اساس عملگرهای هندسی یک فایل PDF، سعی میکند جملات و حروف را کنار هم قرار دهد و در نهایت خروجی متنی بهتری را تولید کند. برای نمونه اینبار خروجی متنی حاصل به صورت زیر خواهد بود:
این خروجی با آنچه که در صفحه نمایش داده میشود تطابق دارد.
استخراج متون فارسی از فایلهای PDF توسط iTextSharp
روشهای فوق با PDFهای فارسی هم کار میکنند اما خروجی حاصل آن مفهوم نیست و نیاز به پردازش ثانوی دارد. ابتدا مثال زیر را درنظر بگیرید:
از متد فوق، برای تولید یک فایل PDF که متنی فارسی را نمایش میدهد استفاده خواهیم کرد. اگر متد readPdf2 را که به همراه LocationTextExtractionStrategy تعریف شده است، بر روی فایل حاصل فراخوانی کنیم، خروجی آن به صورت زیر خواهد بود:
برای تبدیل آن به یونیکد خواهیم داشت:
اکنون خروجی ثبت شده در فایل متنی حاصل به صورت زیر است:
دقیقا به همان نحوی است که iTextSharp و اکثر تولید کنندههای PDF فارسی از آن استفاده میکنند و اصطلاحا چرخاندن حروف یا تولید Glyph mirrors صورت میگیرد. روشهای زیادی برای چرخاندن حروف وجود دارند. در ادامه از روشی استفاده خواهیم کرد که خود ویندوز در کارهای داخلیاش از آن استفاده میکند:
از کلاس فوق در هر برنامهای که راست به چپ را به نحو صحیحی پشتیبانی نمیکند، میتوان استفاده کرد؛ خصوصا برنامههای گرافیکی.
در اینجا برای اصلاح متد readPdf2 خواهیم داشت:
اگر خروجی متد اصلاح شده فوق را بررسی کنیم، دقیقا به «تست میشود» خواهیم رسید.
سؤال: آیا این روش با تمام PDFهای فارسی کار میکند؟
پاسخ: خیر! همانطور که در پیشنیاز مطلب جاری عنوان شد، در یک حالت خاص، PDF writer میتواند شماره Glyphها را کاملا عوض کرده و در فایل PDF نهایی ثبت کند. خروجی حاصل در برنامه Adobe reader خوانا است، چون نمایش را بر اساس اطلاعات هندسی Glyphها انجام میدهد؛ اما خروجی متنی آن به نوعی obfuscated است چون مثلا حرف A آن به کاراکتر مرسوم دیگری نگاشت شده است.
نحوه ذخیره شدن متن در فایلهای PDF
حتما نیاز است پیشنیاز فوق را یکبار مطالعه کنید تا علت خروجیهای متفاوتی را که در ادامه ملاحظه خواهید نمود، بهتر مشخص شوند. همچنین فایل PDF ایی که مورد بررسی قرار خواهد گرفت، همان فایلی است که توسط متد writePdf ذکر شده در پیشنیاز تهیه شده است.
دو کلاس متفاوت برای استخراج متن از فایلهای PDF در iTextSharp وجود دارند:
الف) SimpleTextExtractionStrategy
using System.Diagnostics; using System.IO; using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.text.pdf.parser; namespace TestReaders { class Program { private static void readPdf1() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new SimpleTextExtractionStrategy()); File.WriteAllText("page-" + i + "-text.txt", text); } reader.Close(); } static void Main(string[] args) { readPdf1(); } } }
Test ld Wor llo He Hello People
ب) LocationTextExtractionStrategy
همان مثال قبل را درنظر بگیرید، اینبار به شکل زیر:
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); File.WriteAllText("page-" + i + "-text.txt", text); } reader.Close(); }
Test Hello World Hello People
استخراج متون فارسی از فایلهای PDF توسط iTextSharp
روشهای فوق با PDFهای فارسی هم کار میکنند اما خروجی حاصل آن مفهوم نیست و نیاز به پردازش ثانوی دارد. ابتدا مثال زیر را درنظر بگیرید:
static void writePdf2() { using (var document = new Document(PageSize.A4)) { var writer = PdfWriter.GetInstance(document, new FileStream("test.pdf", FileMode.Create)); document.Open(); FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf"); var tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H); ColumnText.ShowTextAligned( canvas: writer.DirectContent, alignment: Element.ALIGN_CENTER, phrase: new Phrase("تست میشود", tahoma), x: 100, y: 100, rotation: 0, runDirection: PdfWriter.RUN_DIRECTION_RTL, arabicOptions: 0); } Process.Start("test.pdf"); }
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text)); File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8); } reader.Close(); }
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Security; namespace TestReaders { [SuppressUnmanagedCodeSecurity] class GdiMethods { [DllImport("GDI32.dll")] public static extern bool DeleteObject(IntPtr hgdiobj); [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint GetCharacterPlacement(IntPtr hdc, string lpString, int nCount, int nMaxExtent, [In, Out] ref GcpResults lpResults, uint dwFlags); [DllImport("GDI32.dll")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); } [StructLayout(LayoutKind.Sequential)] struct GcpResults { public uint lStructSize; [MarshalAs(UnmanagedType.LPTStr)] public string lpOutString; public IntPtr lpOrder; public IntPtr lpDx; public IntPtr lpCaretPos; public IntPtr lpClass; public IntPtr lpGlyphs; public uint nGlyphs; public int nMaxFit; } public class UnicodeCharacterPlacement { const int GcpReorder = 0x0002; GCHandle _caretPosHandle; GCHandle _classHandle; GCHandle _dxHandle; GCHandle _glyphsHandle; GCHandle _orderHandle; public Font Font { set; get; } public string Apply(string lines) { if (string.IsNullOrWhiteSpace(lines)) return string.Empty; return Apply(lines.Split('\n')).Aggregate((s1, s2) => s1 + s2); } public IEnumerable<string> Apply(IEnumerable<string> lines) { if (Font == null) throw new ArgumentNullException("Font is null."); if (!hasUnicodeText(lines)) return lines; var graphics = Graphics.FromHwnd(IntPtr.Zero); var hdc = graphics.GetHdc(); try { var font = (Font)Font.Clone(); var hFont = font.ToHfont(); var fontObject = GdiMethods.SelectObject(hdc, hFont); try { var results = new List<string>(); foreach (var line in lines) results.Add(modifyCharactersPlacement(line, hdc)); return results; } finally { GdiMethods.DeleteObject(fontObject); GdiMethods.DeleteObject(hFont); font.Dispose(); } } finally { graphics.ReleaseHdc(hdc); graphics.Dispose(); } } void freeResources() { _orderHandle.Free(); _dxHandle.Free(); _caretPosHandle.Free(); _classHandle.Free(); _glyphsHandle.Free(); } static bool hasUnicodeText(IEnumerable<string> lines) { return lines.Any(line => line.Any(chr => chr >= '\u00FF')); } void initializeResources(int textLength) { _orderHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _dxHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _caretPosHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _classHandle = GCHandle.Alloc(new byte[textLength], GCHandleType.Pinned); _glyphsHandle = GCHandle.Alloc(new short[textLength], GCHandleType.Pinned); } string modifyCharactersPlacement(string text, IntPtr hdc) { var textLength = text.Length; initializeResources(textLength); try { var gcpResult = new GcpResults { lStructSize = (uint)Marshal.SizeOf(typeof(GcpResults)), lpOutString = new String('\0', textLength), lpOrder = _orderHandle.AddrOfPinnedObject(), lpDx = _dxHandle.AddrOfPinnedObject(), lpCaretPos = _caretPosHandle.AddrOfPinnedObject(), lpClass = _classHandle.AddrOfPinnedObject(), lpGlyphs = _glyphsHandle.AddrOfPinnedObject(), nGlyphs = (uint)textLength, nMaxFit = 0 }; var result = GdiMethods.GetCharacterPlacement(hdc, text, textLength, 0, ref gcpResult, GcpReorder); return result != 0 ? gcpResult.lpOutString : text; } finally { freeResources(); } } } }
در اینجا برای اصلاح متد readPdf2 خواهیم داشت:
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text)); text = new UnicodeCharacterPlacement { Font = new System.Drawing.Font("Tahoma", 12) }.Apply(text); File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8); } reader.Close(); }
سؤال: آیا این روش با تمام PDFهای فارسی کار میکند؟
پاسخ: خیر! همانطور که در پیشنیاز مطلب جاری عنوان شد، در یک حالت خاص، PDF writer میتواند شماره Glyphها را کاملا عوض کرده و در فایل PDF نهایی ثبت کند. خروجی حاصل در برنامه Adobe reader خوانا است، چون نمایش را بر اساس اطلاعات هندسی Glyphها انجام میدهد؛ اما خروجی متنی آن به نوعی obfuscated است چون مثلا حرف A آن به کاراکتر مرسوم دیگری نگاشت شده است.
پس از ایجاد یک Solution جدید توسط قالب WPF Framework، هشت پروژه به صورت خودکار اضافه خواهند شد:
1) پروژه ریشه که بسته به نامی که در ابتدای کار انتخاب میکنید، تغییر نام خواهد یافت.
برای مثال اگر نام وارد شده در ابتدای کار MyWpfFramework باشد، این پروژه ریشه نیز، MyWpfFramework نام خواهد داشت. از آن صرفا جهت افزودن Viewهای برنامه استفاده میکنیم. کلیه Viewها در پوشه View قرار خواهند گرفت و با توجه به ساختار خاصی که در اینجا انتخاب شده، این Viewها باید از نوع Page انتخاب شوند تا با سیستم راهبری فریم ورک هماهنگ کار کنند.
در داخل پوشه Views، هر بخش از برنامه را میتوان داخل یک زیر پوشه قرار داد. برای مثال قسمت Login سیستم، دارای سه صفحه ورود، نمایش پیام خوش آمد و نمایش صفحه عدم دسترسی است.
متناظر با هر Page اضافه شده، در پروژه MyWpfFramework.Infrastructure یک ViewModel در صورت نیاز اضافه خواهد شد. قرار داد ما در اینجا ترکیب نام View به علاوه کلمه ViewModel است. برای مثال اگر نام View اضافه شده به پروژه ریشه برنامه، LoginPage است، نام ViewModel متناظر با آن باید LoginPageViewModel باشد تا به صورت خودکار توسط برنامه ردیابی و وهله سازی گردد.
این پروژه از کتابخانه MahApps.Metro استفاده میکند و اگر به فایل MainWindow.xaml.cs آن مراجعه کنید، ارث بری پنجره اصلی برنامه را از کلاس MetroWindow مشاهده خواهید نمود. این فایلها نیازی به تغییر خاصی نداشته و به همین نحو در این قالب قابل استفاده هستند.
و در پوشه Resources آن یک سری قلم و آیکون را میتوانید مشاهده نمائید.
2) پروژه MyWpfFramework.Common
در این پروژه کلاسهایی قرار میگیرند که قابلیت استفاده در انواع و اقسام پروژههای WPF را دارند و الزاما وابسته به پروژه جاری نیستند. یک سری کلاسهای کمکی در این پروژه Common قرار گرفتهاند و قسمتهای مختلف سیستم را تغذیه میکنند؛ مانند خواندن اطلاعات از فایل کانفیگ، هش کردن کلمه عبور، یک سری متد عمومی برای کار با EF، کلاسهای عمومی مورد نیاز در حین استفاده از الگوی MVVM، اعتبارسنجی و امثال آن.
در این پروژه از کلاس PageAuthorizationAttribute آن جهت مشخص سازی وضعیت دسترسی به صفحات تعریف شده در پروژه ریشه استفاده خواهد شد.
نمونهای از آنرا برای مثال با مراجعه به سورس صفحه About.xaml.cs میتوانید مشاهده کنید که در آن AuthorizationType.AllowAnonymous تنظیم شده و به این ترتیب تمام کاربران اعتبارسنجی نشده میتوانند این صفحه را مشاهده کنند.
همچنین در این پروژه کلاس BaseViewModel قرار دارد که جهت مشخص سازی کلیه کلاسهای ViewModel برنامه باید مورد استفاده قرار گیرد. سیستم طراحی شده، به کمک این کلاس پایه است که میتواند به صورت خودکار ViewModelهای متناظر با Viewها را یافته و وهله سازی کند (به همراه تمام وابستگیهای تزریق شده به آنها).
به علاوه کلاس DataErrorInfoBase آن برای یکپارچه سازی اعتبارسنجی با EF طراحی شده است. اگر به کلاس BaseEntity.cs مراجعه کنید که در پروژه MyWpfFramework.DomainClasses قرار دارد، نحوه استفاده آنرا ملاحظه خواهید نمود. به این ترتیب حجم بالایی از کدهای تکرای، کپسوله شده و قابلیت استفاده مجدد را پیدا میکنند.
قسمتهای دیگر پروژه Common، برای ثبت وقایع برنامه مورد استفاده قرار میگیرند. استفاده از آنها را در فایل App.xaml.cs پروژه ریشه برنامه ملاحظه میکنید و نیاز به تنظیم خاص دیگری در اینجا وجود ندارد.
3) پروژه MyWpfFramework.DataLayer
کار تنظیمات EF در اینجا انجام میشود (و قسمت عمدهای از آن انجام شده است). تنها کاری که در آینده برای استفاده از آن نیاز است انجام شود، مراجعه به کلاس MyWpfFrameworkContext.cs و افزودن DbSetهای لازم است. همچنین اگر نیاز به تعریف نگاشتهای اضافهتری وجود داشت، میتوان از پوشه Mappings آن استفاده کرد.
در این پروژه الگوی واحد کار پیاده سازی شده است و همچنین سعی شده تمام کلاسهای آن دارای کامنتهای کافی جهت توضیح قسمتهای مختلف باشند.
کلاس MyDbContextBase به اندازه کافی غنی سازی شدهاست، تا در وقت شما، در زمینه تنظیم مباحثی مانند اعتبارسنجی و نمایش پیغامهای لازم به کاربر، یک دست سازی ی و ک ورودی در برنامه و بسیاری از نکات ریز دیگر صرفه جویی شود.
در اینجا از خاصیت ContextHasChanges جهت بررسی وضعیت Context جاری و نمایش پیغامی به کاربر در مورد اینکه آیا مایل هستید تغییرات را ذخیره کنید یا خیر استفاده میشود.
در متد auditFields آن یک سری خاصیت کلاس BaseEntity که پایه تمامی کلاسهای Domain model برنامه خواهد بود به صورت خودکار مقدار دهی میشوند. مثلا این رکورد را چه کسی ثبت کرده یا چه کسی ویرایش و در چه زمانی. به این ترتیب دیگر نیازی نیست تا در برنامه نگران تنظیم و مقدار دهی آنها بود.
کلاس MyWpfFrameworkMigrations به حالت AutomaticMigrationsEnabled تنظیم شده است و ... برای یک برنامه دسکتاپ WPF کافی و مطلوب است و ما را از عذاب به روز رسانی دستی ساختار بانک اطلاعاتی برنامه با تغییرات مدلها، رها خواهد ساخت. عموما برنامههای دسکتاپ پس از طراحی، آنچنان تغییرات گستردهای ندارند و انتخاب حالت Automatic در اینجا میتواند کار توزیع آنرا نیز بسیار ساده کند. از این جهت که بانک اطلاعاتی انتخابی از نوع SQL Server CE نیز عمدا این هدف را دنبال میکند: عدم نیاز به نگهداری و وارد شدن به جزئیات نصب یک بانک اطلاعاتی بسیار پیشرفته مانند نگارشهای کامل SQL Server. هرچند زمانیکه با EF کار میکنیم، سوئیچ به بانکهای اطلاعاتی صرفا با تغییر رشته اتصالی فایل app.config برنامه اصلی و مشخص سازی پروایدر مناسب قابل انجام خواهد بود.
در فایل MyWpfFrameworkMigrations، توسط متد addRolesAndAdmin کاربر مدیر سیستم در آغاز کار ساخت بانک اطلاعاتی به صورت خودکار افزوده خواهد شد.
4) پروژه MyWpfFramework.DomainClasses
کلیه کلاسهای متناظر با جداول بانک اطلاعاتی در پروژه MyWpfFramework.DomainClasses قرار خواهند گرفت. نکته مهمی که در اینجا باید رعایت شود، مزین کردن این کلاسها به کلاس پایه BaseEntity میباشد که نمونهای از آنرا در کلاس User پروژه میتوانید ملاحظه کنید.
BaseEntity چند کار را با هم انجام میدهد:
- اعمال خودکار DataErrorInfoBase جهت یکپارچه سازی سیستم اعتبارسنجی EF با WPF (برای مثال به این ترتیب خطاهای ذکر شده در ویژگیهای خواص کلاسها توسط WPF نیز خوانده خواهند شد)
- اعمال ImplementPropertyChanged به کلاسهای دومین برنامه. به این ترتیب برنامه کمکی Fody که کار Aspect oriented programming را انجام میدهد، اسمبلی برنامه را ویرایش کرده و متدها و تغییرات لازم جهت پیاده سازی INotifyPropertyChanged را اضافه میکند. به این ترتیب به کلاسهای دومین بسیار تمیزی خواهیم رسید با حداقل نیاز به تغییرات و نگهداری ثانویه.
- فراهم آوردن فیلدهای مورد نیاز جهت بازرسی سیستم؛ مانند اینکه چه کسی یک رکورد را ثبت کرده یا ویرایش و در چه زمانی
نقشهای سیستم در کلاس SystemRole تعریف میشوند. به ازای هر نقش جدیدی که نیاز بود، تنها کافی است یک خاصیت bool را در اینجا اضافه کنید. سپس نام این خاصیت در ویژگی PageAuthorizationAttribute به صورت خودکار قابل استفاده خواهد بود. برای مثال به پروژه ریشه مراجعه و به فایل AddNewUser.xaml.cs دقت کنید؛ چنین تعریفی را در بالای کلاس مرتبط مشاهده خواهید کرد:
در اینجا AuthorizationType سه حالت را میتواند داشته باشد:
اگر حالت ApplyRequiredRoles را انتخاب کردید، در پارامتر اختیاری دوم ویژگی PageAuthorization نیاز است نام یک یا چند خاصیت کلاس SystemRole را قید کنید. بدیهی است کاربر متناظر نیز باید دارای این نقشها باشد تا بتواند به این صفحه دسترسی پیدا کند، یا خیر.
5) پروژه MyWpfFramework.Models
در پروژه MyWpfFramework.Models کلیه Modelهای مورد استفاده در UI که الزاما قرار نیست در بانک اطلاعاتی قرارگیرند، تعریف خواهند شد. برای نمونه مدل صفحه لاگین در آن قرار دارد و ذکر دو نکته در آن حائز اهمیت است:
- ویژگی ImplementPropertyChanged کار پیاده سازی INotifyPropertyChanged را به صورت خودکار سبب خواهد شد.
- کلاس پایه DataErrorInfoBase سبب میشود تا مثلا در اینجا اگر از ویژگی Required استفاده کردید، اطلاعات آن توسط برنامه خوانده شود و با WPF یکپارچه گردد.
6) پروژه MyWpfFramework.Infrastructure.csproj
در پروژه MyWpfFramework.Infrastructure.csproj تعاریف ViewModelهای برنامه اضافه خواهند شد.
این پروژه دارای یک سری کلاس پایه است که تنظیمات IoC برنامه را انجام میدهد. برای مثال FrameFactory.cs آن یک کنترل Frame جدید را ایجاد کرده است که کار تزریق وابستگیها را به صورت خودکار انجام خواهد داد. فایل IocConfig آن جایی است که کار سیم کشی کلاسهای لایه سرویس و اینترفیسهای متناظر با آنها انجام میشود. البته پیش فرضهای آن را اگر رعایت کنید، نیازی به تغییری در آن نخواهید داشت. برای مثال در آن scan.TheCallingAssembly قید شده است. در این حالت اگر نام کلاس لایه سرویس شما Test و نام اینترفیس متناظر با آن ITest باشد، به صورت خودکار به هم متصل خواهند شد.
همانطور که پیشتر نیز عنوان شد، در پوشه ViewModels آن، به ازای هر View یک ViewModel خواهیم داشت که نام آن مطابق قرار داد، نام View مدنظر به همراه کلمه ViewModel باید درنظر گرفته شود تا توسط برنامه شناخته شده و مورد استفاده قرار گیرد. همچنین هر ViewModel نیز باید دارای کلاس پایه BaseViewModel باشد تا توسط IoC Container برنامه جهت تزریق وابستگیهای خودکار در سازندههای کلاسها شناسایی شده و وهله سازی گردد.
7) پروژه MyWpfFramework.ServiceLayer
کلیه کلاسهای لایه سرویس که منطق تجاری برنامه را پیاده سازی میکنند (خصوصا توسط EF) در این لایه قرار خواهند گرفت. در اینجا دو نمونه سرویس کاربران و سرویس عمومی AppContextService را ملاحظه میکنید.
سرویس AppContextService قلب سیستم اعتبارسنجی سیستم است و در IocConfig برنامه به صورت سینگلتون تعریف شده است. چون در برنامههای دسکتاپ در هر لحظه فقط یک نفر وارد سیستم میشود و نیاز است تا پایان طول عمر برنامه، اطلاعات لاگین و نقشهای او را در حافظه نگه داری کرد.
8) پروژه MyWpfFramework.Tests
یک پروژه خالی Class library هم در اینجا جهت تعریف آزمونهای واحد سیستم درنظر گرفته شده است.
1) پروژه ریشه که بسته به نامی که در ابتدای کار انتخاب میکنید، تغییر نام خواهد یافت.
برای مثال اگر نام وارد شده در ابتدای کار MyWpfFramework باشد، این پروژه ریشه نیز، MyWpfFramework نام خواهد داشت. از آن صرفا جهت افزودن Viewهای برنامه استفاده میکنیم. کلیه Viewها در پوشه View قرار خواهند گرفت و با توجه به ساختار خاصی که در اینجا انتخاب شده، این Viewها باید از نوع Page انتخاب شوند تا با سیستم راهبری فریم ورک هماهنگ کار کنند.
در داخل پوشه Views، هر بخش از برنامه را میتوان داخل یک زیر پوشه قرار داد. برای مثال قسمت Login سیستم، دارای سه صفحه ورود، نمایش پیام خوش آمد و نمایش صفحه عدم دسترسی است.
متناظر با هر Page اضافه شده، در پروژه MyWpfFramework.Infrastructure یک ViewModel در صورت نیاز اضافه خواهد شد. قرار داد ما در اینجا ترکیب نام View به علاوه کلمه ViewModel است. برای مثال اگر نام View اضافه شده به پروژه ریشه برنامه، LoginPage است، نام ViewModel متناظر با آن باید LoginPageViewModel باشد تا به صورت خودکار توسط برنامه ردیابی و وهله سازی گردد.
این پروژه از کتابخانه MahApps.Metro استفاده میکند و اگر به فایل MainWindow.xaml.cs آن مراجعه کنید، ارث بری پنجره اصلی برنامه را از کلاس MetroWindow مشاهده خواهید نمود. این فایلها نیازی به تغییر خاصی نداشته و به همین نحو در این قالب قابل استفاده هستند.
و در پوشه Resources آن یک سری قلم و آیکون را میتوانید مشاهده نمائید.
2) پروژه MyWpfFramework.Common
در این پروژه کلاسهایی قرار میگیرند که قابلیت استفاده در انواع و اقسام پروژههای WPF را دارند و الزاما وابسته به پروژه جاری نیستند. یک سری کلاسهای کمکی در این پروژه Common قرار گرفتهاند و قسمتهای مختلف سیستم را تغذیه میکنند؛ مانند خواندن اطلاعات از فایل کانفیگ، هش کردن کلمه عبور، یک سری متد عمومی برای کار با EF، کلاسهای عمومی مورد نیاز در حین استفاده از الگوی MVVM، اعتبارسنجی و امثال آن.
در این پروژه از کلاس PageAuthorizationAttribute آن جهت مشخص سازی وضعیت دسترسی به صفحات تعریف شده در پروژه ریشه استفاده خواهد شد.
نمونهای از آنرا برای مثال با مراجعه به سورس صفحه About.xaml.cs میتوانید مشاهده کنید که در آن AuthorizationType.AllowAnonymous تنظیم شده و به این ترتیب تمام کاربران اعتبارسنجی نشده میتوانند این صفحه را مشاهده کنند.
همچنین در این پروژه کلاس BaseViewModel قرار دارد که جهت مشخص سازی کلیه کلاسهای ViewModel برنامه باید مورد استفاده قرار گیرد. سیستم طراحی شده، به کمک این کلاس پایه است که میتواند به صورت خودکار ViewModelهای متناظر با Viewها را یافته و وهله سازی کند (به همراه تمام وابستگیهای تزریق شده به آنها).
به علاوه کلاس DataErrorInfoBase آن برای یکپارچه سازی اعتبارسنجی با EF طراحی شده است. اگر به کلاس BaseEntity.cs مراجعه کنید که در پروژه MyWpfFramework.DomainClasses قرار دارد، نحوه استفاده آنرا ملاحظه خواهید نمود. به این ترتیب حجم بالایی از کدهای تکرای، کپسوله شده و قابلیت استفاده مجدد را پیدا میکنند.
قسمتهای دیگر پروژه Common، برای ثبت وقایع برنامه مورد استفاده قرار میگیرند. استفاده از آنها را در فایل App.xaml.cs پروژه ریشه برنامه ملاحظه میکنید و نیاز به تنظیم خاص دیگری در اینجا وجود ندارد.
3) پروژه MyWpfFramework.DataLayer
کار تنظیمات EF در اینجا انجام میشود (و قسمت عمدهای از آن انجام شده است). تنها کاری که در آینده برای استفاده از آن نیاز است انجام شود، مراجعه به کلاس MyWpfFrameworkContext.cs و افزودن DbSetهای لازم است. همچنین اگر نیاز به تعریف نگاشتهای اضافهتری وجود داشت، میتوان از پوشه Mappings آن استفاده کرد.
در این پروژه الگوی واحد کار پیاده سازی شده است و همچنین سعی شده تمام کلاسهای آن دارای کامنتهای کافی جهت توضیح قسمتهای مختلف باشند.
کلاس MyDbContextBase به اندازه کافی غنی سازی شدهاست، تا در وقت شما، در زمینه تنظیم مباحثی مانند اعتبارسنجی و نمایش پیغامهای لازم به کاربر، یک دست سازی ی و ک ورودی در برنامه و بسیاری از نکات ریز دیگر صرفه جویی شود.
در اینجا از خاصیت ContextHasChanges جهت بررسی وضعیت Context جاری و نمایش پیغامی به کاربر در مورد اینکه آیا مایل هستید تغییرات را ذخیره کنید یا خیر استفاده میشود.
در متد auditFields آن یک سری خاصیت کلاس BaseEntity که پایه تمامی کلاسهای Domain model برنامه خواهد بود به صورت خودکار مقدار دهی میشوند. مثلا این رکورد را چه کسی ثبت کرده یا چه کسی ویرایش و در چه زمانی. به این ترتیب دیگر نیازی نیست تا در برنامه نگران تنظیم و مقدار دهی آنها بود.
کلاس MyWpfFrameworkMigrations به حالت AutomaticMigrationsEnabled تنظیم شده است و ... برای یک برنامه دسکتاپ WPF کافی و مطلوب است و ما را از عذاب به روز رسانی دستی ساختار بانک اطلاعاتی برنامه با تغییرات مدلها، رها خواهد ساخت. عموما برنامههای دسکتاپ پس از طراحی، آنچنان تغییرات گستردهای ندارند و انتخاب حالت Automatic در اینجا میتواند کار توزیع آنرا نیز بسیار ساده کند. از این جهت که بانک اطلاعاتی انتخابی از نوع SQL Server CE نیز عمدا این هدف را دنبال میکند: عدم نیاز به نگهداری و وارد شدن به جزئیات نصب یک بانک اطلاعاتی بسیار پیشرفته مانند نگارشهای کامل SQL Server. هرچند زمانیکه با EF کار میکنیم، سوئیچ به بانکهای اطلاعاتی صرفا با تغییر رشته اتصالی فایل app.config برنامه اصلی و مشخص سازی پروایدر مناسب قابل انجام خواهد بود.
در فایل MyWpfFrameworkMigrations، توسط متد addRolesAndAdmin کاربر مدیر سیستم در آغاز کار ساخت بانک اطلاعاتی به صورت خودکار افزوده خواهد شد.
4) پروژه MyWpfFramework.DomainClasses
کلیه کلاسهای متناظر با جداول بانک اطلاعاتی در پروژه MyWpfFramework.DomainClasses قرار خواهند گرفت. نکته مهمی که در اینجا باید رعایت شود، مزین کردن این کلاسها به کلاس پایه BaseEntity میباشد که نمونهای از آنرا در کلاس User پروژه میتوانید ملاحظه کنید.
BaseEntity چند کار را با هم انجام میدهد:
- اعمال خودکار DataErrorInfoBase جهت یکپارچه سازی سیستم اعتبارسنجی EF با WPF (برای مثال به این ترتیب خطاهای ذکر شده در ویژگیهای خواص کلاسها توسط WPF نیز خوانده خواهند شد)
- اعمال ImplementPropertyChanged به کلاسهای دومین برنامه. به این ترتیب برنامه کمکی Fody که کار Aspect oriented programming را انجام میدهد، اسمبلی برنامه را ویرایش کرده و متدها و تغییرات لازم جهت پیاده سازی INotifyPropertyChanged را اضافه میکند. به این ترتیب به کلاسهای دومین بسیار تمیزی خواهیم رسید با حداقل نیاز به تغییرات و نگهداری ثانویه.
- فراهم آوردن فیلدهای مورد نیاز جهت بازرسی سیستم؛ مانند اینکه چه کسی یک رکورد را ثبت کرده یا ویرایش و در چه زمانی
نقشهای سیستم در کلاس SystemRole تعریف میشوند. به ازای هر نقش جدیدی که نیاز بود، تنها کافی است یک خاصیت bool را در اینجا اضافه کنید. سپس نام این خاصیت در ویژگی PageAuthorizationAttribute به صورت خودکار قابل استفاده خواهد بود. برای مثال به پروژه ریشه مراجعه و به فایل AddNewUser.xaml.cs دقت کنید؛ چنین تعریفی را در بالای کلاس مرتبط مشاهده خواهید کرد:
[PageAuthorization(AuthorizationType.ApplyRequiredRoles, "IsAdmin, CanAddNewUser")]
/// <summary> /// وضعیت اعتبار سنجی صفحه را مشخص میکند /// </summary> public enum AuthorizationType { /// <summary> /// همه میتوانند بدون اعتبار سنجی، دسترسی به این صفحات داشته باشند /// </summary> AllowAnonymous, /// <summary> /// کاربران وارد شده به سیستم بدون محدودیت به این صفحات دسترسی خواهند داشت /// </summary> FreeForAuthenticatedUsers, /// <summary> /// بر اساس نام نقشهایی که مشخص میشوند تصمیم گیری خواهد شد /// </summary> ApplyRequiredRoles }
5) پروژه MyWpfFramework.Models
در پروژه MyWpfFramework.Models کلیه Modelهای مورد استفاده در UI که الزاما قرار نیست در بانک اطلاعاتی قرارگیرند، تعریف خواهند شد. برای نمونه مدل صفحه لاگین در آن قرار دارد و ذکر دو نکته در آن حائز اهمیت است:
[ImplementPropertyChanged] // AOP public class LoginPageModel : DataErrorInfoBase
- کلاس پایه DataErrorInfoBase سبب میشود تا مثلا در اینجا اگر از ویژگی Required استفاده کردید، اطلاعات آن توسط برنامه خوانده شود و با WPF یکپارچه گردد.
6) پروژه MyWpfFramework.Infrastructure.csproj
در پروژه MyWpfFramework.Infrastructure.csproj تعاریف ViewModelهای برنامه اضافه خواهند شد.
این پروژه دارای یک سری کلاس پایه است که تنظیمات IoC برنامه را انجام میدهد. برای مثال FrameFactory.cs آن یک کنترل Frame جدید را ایجاد کرده است که کار تزریق وابستگیها را به صورت خودکار انجام خواهد داد. فایل IocConfig آن جایی است که کار سیم کشی کلاسهای لایه سرویس و اینترفیسهای متناظر با آنها انجام میشود. البته پیش فرضهای آن را اگر رعایت کنید، نیازی به تغییری در آن نخواهید داشت. برای مثال در آن scan.TheCallingAssembly قید شده است. در این حالت اگر نام کلاس لایه سرویس شما Test و نام اینترفیس متناظر با آن ITest باشد، به صورت خودکار به هم متصل خواهند شد.
همانطور که پیشتر نیز عنوان شد، در پوشه ViewModels آن، به ازای هر View یک ViewModel خواهیم داشت که نام آن مطابق قرار داد، نام View مدنظر به همراه کلمه ViewModel باید درنظر گرفته شود تا توسط برنامه شناخته شده و مورد استفاده قرار گیرد. همچنین هر ViewModel نیز باید دارای کلاس پایه BaseViewModel باشد تا توسط IoC Container برنامه جهت تزریق وابستگیهای خودکار در سازندههای کلاسها شناسایی شده و وهله سازی گردد.
7) پروژه MyWpfFramework.ServiceLayer
کلیه کلاسهای لایه سرویس که منطق تجاری برنامه را پیاده سازی میکنند (خصوصا توسط EF) در این لایه قرار خواهند گرفت. در اینجا دو نمونه سرویس کاربران و سرویس عمومی AppContextService را ملاحظه میکنید.
سرویس AppContextService قلب سیستم اعتبارسنجی سیستم است و در IocConfig برنامه به صورت سینگلتون تعریف شده است. چون در برنامههای دسکتاپ در هر لحظه فقط یک نفر وارد سیستم میشود و نیاز است تا پایان طول عمر برنامه، اطلاعات لاگین و نقشهای او را در حافظه نگه داری کرد.
8) پروژه MyWpfFramework.Tests
یک پروژه خالی Class library هم در اینجا جهت تعریف آزمونهای واحد سیستم درنظر گرفته شده است.
نظرات مطالب
ASP.NET MVC #13
در قسمتهای قبل مطرح کردم که میشود attributes را کلا از کلاس مدل خارج کرد:
به این ترتیب میشود از یک مدل با ارث بری چندین viewModel درست کرد. بعد متادیتای آنرا به صورت جداگانه اعمال کرد.
[MetadataType(typeof(Customer_Validation))] public partial class Customer { } public class Customer_Validation { }
ممنون آقای نصیری
با راهنمایی شما مشکل Duplicate Reader با نوشتن دستور به شکل زیر حل شد.
الان مشکلی دارم اینه که نمیتونم یه select خوب بنویسم تا فقط مواردی را که میخوام برگردونم.
مثلا من viewmodel را این شکلی تعریف کردم.
توی select زدن نمیدونم چه شکلی باید کد بزنم تا parent و children هم یه صورت خودکار پر شوند.
در حقیقت من میخوام این موارد را در یک navigation bar به صورت منوی آبشاری نشون بدم.
در ضمن شما میتونید در نشان دادن اطلاعات فوق به صورت منوی آبشاری با امکان تعریف فرزند تو در تو بدون محدودیت راهنماییم کنید.من کدی واسش نوشتم ولی متاسفانه برای پیاده سازی نامحدود بودن فرزندان منو مشکل دارم.
ممنون
با راهنمایی شما مشکل Duplicate Reader با نوشتن دستور به شکل زیر حل شد.
this._pages.Include(page => page.Children).ToList().Where(page => page.Parent == null).ToList();
الان مشکلی دارم اینه که نمیتونم یه select خوب بنویسم تا فقط مواردی را که میخوام برگردونم.
مثلا من viewmodel را این شکلی تعریف کردم.
public class NavBarModel { public virtual int Id { get; set; } public virtual string Title { get; set; } public virtual string Status { get; set; } public virtual int? Order { get; set; } public virtual NavBarModel Parent { get; set; } public virtual ICollection<Page> Children { get; set; } }
توی select زدن نمیدونم چه شکلی باید کد بزنم تا parent و children هم یه صورت خودکار پر شوند.
در حقیقت من میخوام این موارد را در یک navigation bar به صورت منوی آبشاری نشون بدم.
در ضمن شما میتونید در نشان دادن اطلاعات فوق به صورت منوی آبشاری با امکان تعریف فرزند تو در تو بدون محدودیت راهنماییم کنید.من کدی واسش نوشتم ولی متاسفانه برای پیاده سازی نامحدود بودن فرزندان منو مشکل دارم.
ممنون
نظرات مطالب
مراحل تنظیم Let's Encrypt در IIS
- این تصویر مجوز آزمایشی خود ASP.NET Core است.
- در اولین تصویر این مطلب، بررسی کنید چه نوع HTTPS binding ای تعریف شده و جزئیات آن چیست؟
+ این تنظیم را شاید تغییر دادید (همان حالت ignore درست هست):
website -> SSL Settings -> Client Certificates -> Select `Ignore`: This is the default option.
نظرات مطالب
فراخوانی سرویس های WCF به صورت Async
اگر از روش ChannelFactory استفاده کنید به دلیل دسترسی مستقیم به اسمبلی Service Contract تمام Operation Contract های تعریف شده در هر سرویس در دسترس خواهد بود. تابع CreateChannel با استفاده از تنظیمات Binding و EndpointAddress یک کانال به سرویس مربوطه خواهد ساخت و هیچ گونه نیازی به Add service reference در این روش نیست.
نظرات مطالب
C# 7.1 - Tuple Name Inference
یکی از ویژگیهای جالب Tuple اینه که میشه در بدنه For نوع دادهی متفاوتی قرار داد
public static void Main() { var li = Enumerable.Range(1, 10).ToList(); var sb = new StringBuilder(); //for (int i=0,j=1;;) //در اینجا میشه نوعهای متفاوتی تعریف کرد for ( (int i, bool first) = (0, true); i < li.Count; i++, first = false) { if (!first) sb.Append(", "); sb.Append(li[i]); } Console.WriteLine(sb.ToString()); }
بله باید دستی تغییر کند ، راه خودکاری نمیدانم متاسفانه
کلا بهتر است از viewmodel به جای استفاده از خود مدل استفاده کرد ولی به هرحال viewmodel نیز پس از تغییر دیتابیس باید دستی تغییر کند