JSON Web Token is a security token which acts as a container for claims about the user, it can be transmitted easily between the Authorization server (Token Issuer), and the Resource server (Audience), the claims in JWT are encoded using JSON which make it easier to use especially in applications built using JavaScript.
- فعال سازی فشرده سازی
- انتقال لاگها به یک سیستم راه دور
- حذف لاگ فایلهای قدیمی از طریق اسکریپت نویسی
- حذف لاگ فایلهای قدیمی توسط IIS Log File Cleaner
فشرده سازی دایرکتوری لاگ فایل ها
%SystemDrive%\inetpub\logs\LogFiles
این روش سادهترین روش موجود برای مدیریت لاگ هاست ولی روش نهایی نیست و باز به مرور زمان این روش هم کارایی خودش را از دست خواهد داد. این روش بیشتر شبیه خرید زمان میباشد تا اینکه یک راه حل نهایی برای حل مشکل باشد. البته این را هم باید مدنظر داشت که موقع تیک زدن گزینه بالا عملیات فشرده سازی باعث کند شدن سرعت کامپیوتر در حین آغاز عمل ذخیره سازی لاگ فایلها هم خواهد شد. پس اگر قصد چنین کاری ذا دارید در ساعاتی که سرور کمترین فشار از طرف کاربران را دارد یا اصطلاحا پیک کاری آن پایین است انجامش دهید.
انتقال لاگ فایلها به یک سیستم راه دور
همانطور که در بالا اشاره کردیم محل پیش فرض ذخیره سازی لاگها درمسیر
%SystemDrive%\inetpub\logs\LogFiles
قرار دارد و این محل ذخیره سازی برای هر سرور یا حتی یک وب سایت خاص در صفحه تنظیمات Logging مشخص شده است و شما در میتوانید این لاگها را حتی برای کل سرور یا مربوط به یک سایت خاص، به سروری دیگر انتقال دهید. این امکان میتواند به امنیت سیستم هم کمک فراوانی کند تا اگر دیسک محلی Local Disk هم دچار مشکل شد، باز خواندن لاگ فایلها میسر باشد و با استفاده از ابزارهای تحلیل لاگ فایل ها، آنها را مورد بررسی قرار دهیم. برای تغییر محل ذخیره سازی لاگها به یک سیستم راه دور، راه حل زیر را طی کنید.
در IIS وب سایتی را که میخواهید لاگ آن انتقال یابد، انتخاب کنید؛ یا اگر لاگ کل سیستم IIS را میخواهید انتقال بدهید نام سرور را در لیست درختی انتخاب کنید و از ماژولهای سمت راست، ماژول Logging را انتخاب کنید و در قسمت Directory که محل ذخیره سازی فعلی لاگها را نوشته شده است، به صورت UNC آدرس دهی کنید. در آدرس زیر اولی نام سرور است Contoso-server1\\ و دومی هم Logs نام پوشهای که به اشتراک گذاشته شده است.
حذف لاگ فایلهای قدیمی با استفاده از اسکریپت
با این روش میتوانید لاگ فایل هایی را که بعد از مدتی معین که دلخواه شما هست، از سیستم حذف نمایید و اگر این اسکریپت را زمان بندی خودکار نمایید، میتوانید از مراقبت مداوم و ثابت این کار نیز رها شوید.
با ستفاده از VBScript بررسی میکنیم که اگر مثلا عمر لاگ فایل به 30 روز رسیده است، باید حذف شوند. خط دوم کد زیر نهایت عمر یک لاگ فایل را مشخص میکند:
sLogFolder = "c:\inetpub\logs\LogFiles" iMaxAge = 30 'in days Set objFSO = CreateObject("Scripting.FileSystemObject") set colFolder = objFSO.GetFolder(sLogFolder) For Each colSubfolder in colFolder.SubFolders Set objFolder = objFSO.GetFolder(colSubfolder.Path) Set colFiles = objFolder.Files For Each objFile in colFiles iFileAge = now-objFile.DateCreated if iFileAge > (iMaxAge+1) then objFSO.deletefile objFile, True end if Next Next
اسکریپت بالا تمامی subfolderها را برای همه سایتها بررسی کرده و لاگهای آنان را حذف میکند. ولی اگر دوست دارید این عملیات را تنها به یک وب سایت محدود کنید، باید مسیر را در خط اول دقیقتر مشخص کنید.
برای اجرای دستی اسکریپت در cmd تایپ کنید:
cscript.exe c:\scripts\retentionscript.vbs
ولی اگر میخواهید این اسکریپت در هر دورهی زمانی خاص اجرا شود، یا زمان بندی Scheduling گردد، دیگر مجبور نیستید هر بار به فکر نگهداری از لاگها باشید.
زمان بندی اجرای اسکریپت
server manager (قابل تست در ویندوزهای سرور) را باز کرده و از منوی Tools گزینه Task Scheduler را انتخاب کنید و در قسمت Actions گزینه Create Task را انتخاب نمایید. در کادر باز شده نام "Delete Log Files " را برای مثال برگزینید و در قسمت Security هم کاربری که اجازه اجرای اسکریپت را دارد مشخص کنید.
برگه Triggers را انتخاب کرده و گزینه New را انتخاب کنید و عملیات زمان بندی را تنظیم کنید و حتما بعد از زمان بندی مطمئن باشید که تیک Enabled فعال است.
در برگه Actions هم گزینه New را انتخاب کنید؛ در کادر باز شده از لیست Start a program را انتخاب کرده و در قسمت Program\script، دستور cscript را ذکر نمایید و به عنوان آرگومان ورودی Add arguments هم مسیر اسکریپت خود را ذکر نمایید و کادر را تایید کنید.
برای آغاز زمان بندی در لیست وظیفههای فعال active task pane، وظیفه ای که الان ساخته اید را اجرا کرده و به مسیر ذخیره لاگها رفته و میبینید که لاگهای مورد نظر حذف شدهاند؛ پس از صحت اجرای اسکریپت مطمئن میشویم. دوباره به لیست وظایف رفته و گزینه End را بزنید تا وظیفه، در حالت Ready قرار گیرد تا از همین الان فرایند زمان بندی اجرای اسکریپت آغاز شود.
حذف لاگ فایلها با استفاده از IIS Log Cleaner Tools
سادهترین ابزار برای مدیریت حذف لاگ فایل هاست که هر یک ساعت یکبار اجرا شده و لاگ فایلهای تاریخ گذشته را که زمانش را شما تعیین میکنید، به سمت سطل زباله که البته درستش بازیافت است Recycle Bin انتقال میدهد تا از ضرر از دست دادن لاگها جلوگیری کند که بعدا شما میتوانید آنها را به صورت دستی حذف کنید. همچنین عملیات خودکار حذف را نیز میتوان متوقف نمود.
ابتدا برنامه را از اینجا دانلود کنید. موقعیکه برنامه را اجرا کنید، در نوتیفیکیشن taskbar مینشیند و برنامه با یک پیغام به شما اعلام میکند، این اولین بار است که برنامه را باز کردهاید. پس یک سر به setting آن بزنید؛ با انتخاب گزینهی settings برنامه بسته شده و فایل Settings.txt برای شما باز میشود که مدت زمان عمر لاگ فایل و مسیر ذخیره آنها، از شما پرسیده میشود که مقدار عمر هر لاگ فایل به طور پیش فرض 30 روز و مسیر ذخیرهی لاگها همان مسیر پیش فرض IIS است که اگر شما دستی آن را تغییر داده اید، با پرسیدن آن، از محل لاگها اطمینان کسب میکند. در صورتی که قصد تغییری را در فایل، دارید آن را تغییر داده و ذخیره کنید و برنامه را مجددا اجرا کنید.
نکات نهایی در مورد این برنامه :
- اگر از ابزار IIS Cleaner Tool استفاده میکنید باید دستی سطل بازیافت را هم پاک کنید و هم اینکه میتوانید یک محدودیت حجمی برای Recycle Bin قرار دهید که اگر به یک حدی رسید، خودکار پاک کند تا مشکلی برای سیستم عامل ایجاد نشود که البته به طور پیش فرض چنین است.
- برنامه بالا به طور پیش فرض ریشهی لاگها را حذف میکند. پس اگر میخواهید فقط سایت خاصی را مد نظر داشته باشد، آدرس دایرکتوری آن را اضافه کنید. البته چون این برنامه فقط روی یک دایرکتوری کار میکند و شما چند وب سایت دارید و مثلا میخواهید سه تای آنها را پاکسازی کنید، چارهی جز استفاده از اسکریپتهای با زمان بندی ندارید.
- برنامهی بالا فقط فایل هایی با پسوند log را به سطل بازیافت انتقال میدهد.
- برنامهی بالا یک سرویس نیست و باید به طور دستی توسط کاربر اجرا گردد. پس اگر ریست هم شد باید دستی اجرا شود یا آن را به داخل پوشه startup بکشید.
- برنامه برای اجرایش نیاز به لاگین کاربر و مجوز نوشتن در آن پوشه را دارد تا به درستی کار کند.
(The God Class Problem (Behavioral Form
یکی از مخاطراتی که ممکن است موجب عدم بروز مزایای شیء گرایی در طرح شما شود، بحث God Class میباشد. شکل رفتاری آن (Behavioral Form) بیشتر در اثر یک خطای مشترک بین توسعه دهندگان پارادایم action-oriented و در جریان مهاجرت به سمت پارادایم شیء گرا، رخ میدهد.
این توسعه دهندگان بیشتر سعی در تسخیر و دستیابی به یک مکانیزم کنترل مرکزی شبیه به آنچه در پارادایم action-oriented داشتهاند، در طراحی شیء گرای خود دارند. حاصل این کار تشکیل کلاسی خواهد بود که همه کارها را انجام میدهد، درحالیکه جزئیات ناچیزی هم به عهده مجموعهای از کلاسها سپرده شده است.
قاعده شهودی 3.1
تا حد ممکن هوشمندی سیستم را به صورت افقی و به طور یکنواخت توزیع کنید. به این معنی که کلاسهای سطح بالای موجود در طراحی، باید کار را به طور یکسان مابین خود به اشتراک بگذارند. (Distribute system intelligence horizontally as uniformly as possible, that is, the top-level classes in a design should share the work uniformly)منظور اینکه Businessای را که سیستم قرار است پیاده سازی کند، بین کلاسهای سطح بالا تقسیم کنید. در حالت vertical یا عمودی میتوان در نظر گرفت که کلاسی این Business را توسط یکسری متد در دل خود پیاده سازی کند و این متدها یکدیگر را فراخوانی خواهند کرد.
قاعده شهودی 3.2
در سیستم خود God Class ایجاد نکنید. به کلاس هایی که نام آنها شامل Driver، Manager و یا Subsystem میباشد، مشکوک باشید. (Do not create god classes/objects in your system. Be very suspicious of a class whose name contains Driver, Manager, System, or Subsystem)
مانند: BlahBlahSystem یا BlahManager
قاعده شهودی 3.3مراقب کلاس هایی باشید که در واسط عمومی آنها تعداد زیادی Accessor Method تعریف شده است؛ وجود آنها نشان از این دارد که داده و رفتار، در یکجا نگه داشته نشده اند. (Beware of classes that have many accessor methods defined in their public interface. Having many implies that related data and behavior are not being kept in one place)ازدیاد عملیات get و set در واسط عمومی کلاسها که Accessor Method نامیده میشوند، نشان دهنده ایجاد شکل رفتاری God Class میباشند. منظور این است که یک کلاس، رفتارهایی برای کار کردن با دادههای خود در نظر نگرفته است و این دادهها را از طریق accessor methodها در اختیار کلاس دیگری قرار میدهد تا عملیات روی دادهها را انجام دهد. در اینجا هم مقصود God Class شدن کلاسی است که از این accessor methodها استفاده میکند و نشان از این دارد که تعداد رفتارهای آن زیاد خواهد شد.
مراقب کلاس هایی باشید که تعداد خیلی زیادی رفتار نامرتبط دارند؛ یعنی رفتارهایی که فقط برروی زیر مجموعه ای از دادههای کلاس کار میکنند. God Classها اغلب دارای اینگونه رفتارهای نامرتبط به هم هستند. (Beware of classes that have too much noncommunicating behavior, that is, methods that operate on a proper subset of the data members of a class. God classes often exhibit much noncommunicating behavior)منظور اینکه کلاس مورد نظر را میتوان شکست و تبدیل به دو کلاس مختلف کرد. به عنوان اولین مثال، دامنه مربوط به سیستم برنامه ریزی دورههای آموزشی را در نظر بگیرید. در این دامنه، ما با وهله هایی از «Student» ،«Course» و «CourseOffering» سروکار خواهیم داشت.
قصد داریم با فراخوانی متد ()add_student مربوط به CourseOffering، یک دانشجو را به لیست شرکت کنندگان یک دوره اضافه کنیم. همچنین در این زمان لازم است مطمئن شویم که دانشجوی مورد نظر تمام پیش نیازهای دوره انتخاب شده را گذرانده باشد. به نظر شما کدام کلاس میبایست عملیات چک کردن پیش نیازها را انجام دهد؟
کلاس دانشجو از دورههایی که گذرانده است آگاه است و کلاس دوره هم از پیش نیازهای خود. در بهترین حالت شاید یکی از طراحیهای زیر را ارائه دهید. به شکلی که یا کلاس دوره با استفاده از متد get_courses مربوط به کلاس دانشجو، داده مورد نیاز را بدست آورده و عملیات چک کردن را در دل خود بگنجاند یا برعکس.
در هر دو طراحی بالا، بخشی از اطلاعات مربوط به policy (سیاست) در کلاس هایی قرار دارد که موضوع تصمیمات سیاستها هستند. این کار از آن جهت که کلاسهای مورد نظر ما را به دامنه خاصی که این policy در آن دامنه معنا دارد وابسته میکند و امکان استفاده مجدد از آن کلاسها را از دست خواهید داد.
راه حلهای پیشنهادی برای مشکل مطرح شده به شکل زیر میباشند:
با توجه به طراحی شکل بالا، یا خود کلاس CourseOffering با استفاده از لیست دورههای گذرانده شده توسط دانشجو و لیست دورههای پیش نیاز دوره انتخابی، چک کردن را انجام دهد و یا کلاسی با عنوان PrereqChecker که یک Controller Class (کلاسی که فقط رفتار دارد و داده مورد نظر خود را توسط سایر کلاسها و از طریق accessor methodهای آنها تأمین میکند) میباشد، وظیفه چک کردن را برعهده بگیرد.
علاوه بر اینکهaccessor method ها، داده را برای کلاسهای کنترلر مهیا میکنند (مانند مثال بالا)، وظیفهی مهیا کردن داده برای UI (رابط کاربری) را نیاز بر عهده خواهند داشت. به این صورت که رابط کاربری، با استفاده از این متدها، مشخصات درونی مدل را نمایش دهد و یا این امکان را به کاربر میدهد که این مشخصات درونی مدل را ویرایش کرده و به سمت مدل ارسال نماید.
قاعده شهودی 3.5
در برنامههایی که شامل یک مدل شی گرایی میباشند که با رابط کاربری تعامل دارند، مدل نباید به رابط کاربری وابسته باشد. رابط کاربری میبایست وابسته به مدل باشد. (In applications that consist of an object-oriented model interacting with a user interface, the model should never be dependent on the interface. The interface should be dependent on the model)
برای عدم نقض این قاعده شهودی، لازم است در مدل یکسری accessor method در نظر گرفته شود تا رابط کاربری از آن استفاده کند؛ ولی باید مراقب بود که این accessor methodها صرفا توسط رابط کاربری استفاده شود و عدم توجه به این قضیه، احتمالا شما را به سمت نقض قاعده 3.3 متمایل خواهد کرد.
Ionic , react native , flutter , xamarin ….
مزایای نوشتن یک اپلیکیشن با فریم ورکها:
1- نوشتن کد به مراتب آسانتر است.
۲- چون اکثر فریم ورکها، فریم ورکهای جاوا اسکریپتی هستند، سرعت بالایی هنگام run کردن برنامه دارند ولی در build آخر و خروجی نهایی بر روی پلتفرم، این سرعت کندتر میشود.
۳- cossplatform بودن. با طراحی یک اپلیکیشن برای یک پلتفرم میتوان همزمان برای پلتفرمهای دیگر خروجی گرفت.
۱- برنامه نویس را محدود میکند.
۲- سرعت اجرای پایینی دارد.
۳- حجم برنامه به مراتب بالاتر میباشد.
۴- از منابع سخت افزاری بیشتری استفاده میکند.
اگر شما هم میخواهید از فریم ورکها برای طراحی اپلیکیشن استفاده کنید در اینجا میتوانید اطلاعات بیشتری را درباره هر کدام از فریم ورکها ببینید. هر کدام از این فریم ورکها دارای مزایا معایب و همچنین رزومه کاری خوب میباشند. بیشتر فریم ورکهای بالا در رندر آخر، همان کد native را تولید میکنند؛ مثلا اگر برنامهای را با react native بنویسید، میتوانید همان برنامه را بر روی اندروید استودیو و کد native بالا بیارید. توضیحات بالا مقایسه فریم ورکهای Cross Platform با زبانهای native بود. اما در این مقاله قصد آشنایی با تکنولوژی جدیدی را داریم که شما را قادر میسازد یک وب اپلیکیشن را با آن طراحی کنید.
pwa مخفف Progressive Web Apps است و یک تکنولوژی برای طراحی وب اپلیکیشنهای تحت مرورگر میباشد. شما با pwa میتوانید اپلیکیشن خود را بر روی پلتفرمهای مختلفی اجرا کنید، طوری که کاربران متوجه نشوند با یک صفحهی وبی دارند کار میکنند. شاید شما هم فکر کنید این کار را هم میتوان با html ,css و responsive کردن صفحه انجام داد! ولی اگر بخواهید کاربر متوجه استفادهی از یک صفحهی وبی نشود، باید حتما از pwa استفاده کنید! برای مثال یک صفحهی وبی معمولی حتما باید در بستر اینترنت اجرا شود، ولی با pwa با یکبار وصل شدن به اینترنت و کش کردن دادهها، برای بار دوم دیگر نیازی به اینترنت ندارد و میتواند به صورت offline کار کند. شما حتی با pwa میتوانید اپلیکیشن را در background اجرا کنید و notification ارسال کنید.
۱- یکی از مزیتهای مهم pwa، حالت offline آن میباشد که حتی با قطع اینترنت، شما میتوانید همچنان با اپلیکیشن کار کنید.
۲- با توجه به اینکه شما در حقیقت با یک صفحهی وبی کار میکنید، دیگر نیازی به دانلود و نصب ندارید.
۳- امکان بهروز رسانی کردن، بدون اعلام کردن نسخه جدید.
۴- از سرعت بسیار زیادی برخوردار است.
۵- چون pwa از پروتکل https استفاده میکند، دارای امنیت بالایی میباشد.
۱- محدود به مرورگر میباشد.
۲- هرچند امروزه اکثر مرورگرها pwa را پشتیبانی میکنند، ولی در بعضی از مرورگرها و مرورگرهای با ورژن پایین، pwa پشتیبانی نمیشود.
3- نمیتوان یک برنامهی مبتنی بر os را نوشت و محدود به مرورگر میباشد
1- برای pwa لزومی ندارد حتما از فریم ورکهای spa استفاده کنید. شما از هر فریم ورک Client Side ای میتوانید استفاده کنید.
۲- چون صفحات شما در پلتفرمهای مختلف و با صفحه نمایشهای مختلفی اجرا میشود، باید صفحات به صورت کاملا responsive شده طراحی شوند.
۳- باید از پروتکل https استفاده کنید.
ما در این مقاله از فریم ورک angular استفاده خواهیم کرد.
قبل از شروع، با شیوه کار pwa آشنا خواهیم شد. یکی از قسمتهای مهم Service Worker ،pwa میباشد که از جمله کش کردن، notification فرستادن و اجرای پردازشها در پس زمینه را بر عهده دارد.
چند نکته در رابطه با Service Worker
- نباید برای نگهداری داده global از Service Worker استفاد کرد. برای استفاده از دادههای Global میتوان از Local Storage یا IndexedDB استفاده کرد.
- service worker به dom دسترسی ندارند.
قبل از شروع، سناریوی پروژه را تشریح خواهیم کرد. رکن اصلی یک برنامهی وب، UI آن میباشد و قصد داریم کاربران را متوجه کار با یک صفحهی وبی نکنیم. قالبی که ما در این مثال در نظر گرفتیم خیلی شبیه به یک اپلیکیشن پلتفرم اندرویدی میباشد. یک کاربر با کشیدن منوی کشویی میتواند گزینههای خود را انتخاب نمایند. اولین گزینهای که قصد پیاده سازی آن را داریم، ثبت کاربران میباشد. بعد از ثبت کاربران در یک Component جدا، کاربران را در یک جدول نمایش خواهیم داد.
1- سرویس crud را به صورت کامل در پروژه قرار خواهیم داد، ولی چون از حوصلهی مقاله خارج است، فقط ثبت کاربران و نمایش کاربران را پیاده سازی خواهیم کرد.
شروع به کار
پیش نیازهای یک پروژهی انگیولاری را بر روی سیستم خود فراهم کنید. ما در این مثال از یک template آماده انگیولاری استفاده خواهیم کرد. پس برای اینکه با جزئیات و طراحی ui درگیر نشویم، از لینک github پروژه را دریافت کنید.
سپس وارد root پروژه شوید و با دستور زیر پکیجهای پروژه را نصب کنید:
npm install
قبل از اینکه کاری را انجام دهید، چند بار صفحه را refresh کنید! صفحه بدون هیچ تغییری refresh میشود. اینبار گزینهی offline را فعال کنید و مجددا صفحه را refresh کنید.
نصب pwa بر روی پروژه
برای اضافه کردن pwa به پروژه وارد ریشهی پروژه شوید و دستور زیر را وارد کنید:
ng add @angular/PWA
Manifest.json : اگر محتویات فایل را مشاهده کرده باشید، شامل تنظیمات فنی وب اپلیکیشن میباشد؛ از جمله Home Screen Icon و نام وب اپلیکیشن و سایر تنظیمات دیگر.
Nsgw-config.json : این فایل نسبت به فایل manifest فنیتر میباشد و بیشتر به کانفیگ مد آفلاین و کش کردن مرتبط میشود. در ادامه با این فایل بیشتر کار داریم.
اجرا کردن وب اپلیکیشن
برای اجرا کردن و نمایش خروجی از وب اپلیکیشن، ابتدا باید از پروژه build گرفت. با استفاده از دستور زیر از پروژه خود build بگیرید:
ng build -- prod
cd dist/Web Application Pwa
Http-server -o
npm i http-server
برسی فایل Nsgw-config.json
وارد فایل Nsgw-config.json شوید:
"$schema": "./node_modules/@angular/service-worker/config/schema.json", "index": "/index.html", "assetGroups": [ { "name": "app", "installMode": "prefetch", "resources": { "files": [ "/favicon.ico", "/index.html", "/*.css", "/*.js" ] } }, { "name": "assets", "installMode": "lazy", "updateMode": "prefetch", "resources": { "files": [ "/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" ] } } ], "dataGroups": [ { "name": "api-performance", "urls": [ "https://api/**" ], "cacheConfig": { "strategy": "performance", "maxSize": 100, "maxAge": "3d" } } ] }
۱- assetGroups : کش کردن اطلاعات مربوط به اپلیکیشن
۲- Index : کش کردن فایل مربوط به index.html
۳- assetGroups : کش کردن فایلهای مربوط به asset، شامل فایلهای js، css و غیره
۴- dataGroups : این object مربوط به وقتی است که برنامه در حال اجرا است. میتوان دادههای در حال اجرای اپلیکیشن را کش کرد. دادهی در حال اجرا میتواند شامل فراخوانی apiها باشد. برای مثال فرض کنید شما در حالت کار کردن online با اپلیکیشن، لیستی از اسامی کاربران را از api گرفته و نمایش میدهید. وقتی دفعهی بعد در حالت offline اپلیکیشن را باز کنیم، اگر api را کش کرده باشیم، اپلیکیشن دادهها را از کش فراخوانی میکند. این عمل درباره post کردن دادهها هم صدق میکند.
خود dataGroups شامل چند object زیر میباشد:
۱- name : یک نام انتخابی برای Groups میباشد.
۲- urls : شامل آرایهای از آدرسها میباشد. میتوان آدرس یک دومین را همراه با کل apiها به صورت زیر کش کرد:
"https://api/**"
۱- maxSize : حداکثر تعداد کشهای مربوط به Groups .
۲- maxAge : حداکثر lifetime مربوط به کش.
۳- strategy : که میتواند یکی از مقادیر freshness به معنی Network-First یا performance به معنی Cache-First باشد.
پیاده سازی پیغام نمایش بهروزرسانی جدید وب اپلیکیشن
در اپلیکیشنهای native وقتی بهروزرسانی جدیدی برای app اعلام میشود، در فروشگاهای اینترنتی پیغامی مبنی بر بهروزرسانی جدید app برای کاربران ارسال میشود که کاربران میتوانند app خود را بهروزرسانی کنند. ولی در pwa تنها با یک رفرش صفحه میتوان اپلیکیشن را به جدیدترین امکانات بهروزرسانی کرد! برای اینکه بتوانیم با هر تغییر، پیغامی را جهت بهروزرسانی نسخه یا هر پیغامی دیگری را نمایش دهیم، از کد زیر استفاده میکنم:
if(this.swUpdate.isEnabled) { this.swUpdate.available.subscribe(()=> { if(confirm("New Version available.Load New Version?")){ window.location.reload(); } }) }
در منوی سمت چپ، بر روی database کلیک کنید و یک دیتابیس را در حالت test mode ایجاد نماید. سپس یک collection را به نام user ایجاد کرده و فیلدهای زیر را به آن اضافه کنید:
Age :number Fullname :string Mobile : string
npm install --save firebase @angular/fire
@NgModule({ declarations: [AppComponent, RegisterComponent,AboutComponent, UserListComponent], imports: [ BrowserModule, FormsModule, HttpClientModule, HttpClientModule, SharedModule, AppRoutingModule, AngularFireModule.initializeApp(environmentFirebase.firebase), AngularFireDatabaseModule, ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), ], providers: [FirebaseService], bootstrap: [AppComponent] }) export class AppModule {}
export const environment ={ production: true, firebase: { apiKey: "AIz×××××××××××××××××××××××××××××××××8", authDomain: "pwaangular-6c041.firebaseapp.com", databaseURL: "https://pwaangular-6c041.firebaseio.com", projectId: "pwaangular-6c041", storageBucket: "pwaangular-6c041.appspot.com", messagingSenderId: "545522081966" } }
FirebaseService در قسمت providers مربوط به سرویس crud میباشد. اگر وارد فایل مربوطه شوید، چند عمل اصلی به صورت زیر در آن پیاده سازی شده است:
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import * as firebase from 'firebase'; import { AngularFireDatabase } from '@angular/fire/database'; import { HttpClient } from '@angular/common/http'; import { map } from 'rxjs/operators'; import { ThrowStmt } from '@angular/compiler'; @Injectable({ providedIn: 'root' }) export class FirebaseService { ref = firebase.firestore().collection('users'); constructor(public db: AngularFireDatabase,public _http:HttpClient) { } getUsers(): Observable<any> { return new Observable((observer) => { this.ref.onSnapshot((querySnapshot) => { let User = []; querySnapshot.forEach((doc) => { let data = doc.data(); User.push({ key: doc.id, fullname: data.fullname, age: data.age, mobile: data.mobile }); }); observer.next(User); }); }); } getUser(id: string): Observable<any> { return new Observable((observer) => { this.ref.doc(id).get().then((doc) => { let data = doc.data(); observer.next({ key: doc.id, title: data.title, description: data.description, author: data.author }); }); }); } postUser(user): Observable<any> { return new Observable((observer) => { this.ref.add(user).then((doc) => { observer.next({ key: doc.id, }); }); }); } updateUser(id: string, data): Observable<any> { return new Observable((observer) => { this.ref.doc(id).set(data).then(() => { observer.next(); }); }); } deleteUser(id: string): Observable<{}> { return new Observable((observer) => { this.ref.doc(id).delete().then(() => { observer.next(); }); }); } getDataOnApi(){ return this._http.get('https://site.com/api/General/Getprovince') .pipe( map((res: Response) => { return res; }) ); } getOnApi(){ return this._http.get("https://site.com/api/General/Getprovince",).pipe( map((response:any) => { return response } ) ); } }
با دستورات زیر میتوانید مجددا پروژه را اجرا کنید:
ng build --prod cd dist/Pwa-WepApp Http-server -o
تست وب اپلیکیشن بر روی پلتفرمهای مختلف
برای اینکه بتوانیم خروجی وب اپلیکیشن را بر روی پلتفرمهای مختلفی تست کنیم، میتوانیم آن را آپلود کرده و مثل یک سایت اینترنتی، با وارد کردن دومین، وارده پروژه شد. ولی کم هزینهترین راه، استفاده از ابزار ngrok میباشد. میتوانید توسط این مقاله پروژه خودتان در لوکال بر روی https سوار کنید.
نکته! توجه کنید apiهای مربوط به firebase را نمیتوان کش کرد.
کدهای مربوط به این قسمت را میتوانید از این repository دریافت کنید .
کلاس ArrayList
using System; using System.Collections; class ProgrArrayListExample { static void Main() { ArrayList list = new ArrayList(); list.Add("Hello"); list.Add(5); list.Add(3.14159); list.Add(DateTime.Now); for (int i = 0; i < list.Count; i++) { object value = list[i]; Console.WriteLine("Index={0}; Value={1}", i, value); } } }
Index=0; Value=Hello Index=1; Value=5 Index=2; Value=3.14159 Index=3; Value=29.02.2015 23:17:01
ArrayList list = new ArrayList(); list.Add(2); list.Add(3.5f); list.Add(25u); list.Add(" ریال"); dynamic sum = 0; for (int i = 0; i < list.Count; i++) { dynamic value = list[i]; sum = sum + value; } Console.WriteLine("Sum = " + sum); // Output: Sum = 30.5ریال
مجموعههای جنریک Generic Collections
GenericType<T> instance = new GenericType<T>();
List<int> intList = new List<int>(); List<bool> boolList = new List<bool>(); List<double> realNumbersList = new List<double>();
List<int> intList = new List<int>();
- استفاده از index برای دسترسی به یک مقدار، صرف نظر از اینکه چه میزان دادهای در آن وجود دارد، بسیار سریع انجام میگیرد.
- جست و جوی یک عنصر بر اساس مقدار: جست و جو خطی است در نتیجه اگر مقدار مورد نظر در آخرین خانهها باشد بدترین وضعیت ممکن رخ میدهد و بسیار کند عمل میکند. داده هر چی کمتر بهتر و هر چه بیشتر بدتر. البته اگر بخواهید مجموعهای از مقدارهای برابر را برگردانید هم در بدترین وضعیت ممکن خواهد بود.
- حذف و درج (منظور insert) المانها به خصوص موقعی که انتهای آرایه نباشید، شیفت پیدا کردن در آرایه عملی کاملا کند و زمانبر است.
- موقعی که عنصری را بخواهید اضافه کنید اگر ظرفیت آرایه تکمیل شده باشد، نیاز به عمل زمانبر افزایش ظرفیت خواهد بود که البته این عمل به ندرت رخ میدهد و عملیات افزودن Add هم هیچ وابستگی به تعداد المانها ندارد و عملی سریع است.
- افزودن به انتهای لیست به خاطر این که همیشه گره آخر در tail وجود دارد بسیار سریع است.
- عملیات درج insert در هر موقعیتی که باشد اگر یک اشاره گر به آن محل باشد یک عملیات سریع است یا اینکه درج در ابتدا یاانتهای لیست باشد.
- جست و جوی یک مقدار چه بر اساس اندیس باشد و چه مقدار، کار جست و جو کند خواهد بود. چرا که باید تمامی المانها از اول به آخر اسکن بشن.
- عملیات حذف هم به خاطر اینکه یک عمل جست و جو در ابتدای خود دارد، یک عمل کند است.
استفاده از لیست پیوندی برای پیاده سازی پشته:
Stack<string> stack = new Stack<string>(); stack.Push("A"); stack.Push("B"); stack.Push("C"); while (stack.Count > 0) { string letter= stack.Pop(); Console.WriteLine(letter); } //خروجی //C //B //A
ابتدای آرایه مکانی است که عنصر از آنجا برداشته میشود و Head به آن اشاره میکند و tail هم به انتهای آرایه که جهت درج عنصر جدید مفید است. با برداشتن هر خانهای که head به آن اشاره میکند، head یک خانه به سمت جلو حرکت میکند و زمانی که Head از tail بیشتر شود، یعنی اینکه دیگر عنصری یا المانی در صف وجود ندارد و head و Tail به ابتدای صف حرکت میکنند. در این حالت موقعی که المان جدیدی قصد اضافه شدن داشته باشد، افزودن، مجددا از اول صف آغاز میشود و به این صفها، صف حلقوی میگویند.
عملیات اصلی صف دو مورد هستند enqueue که المان جدید را در انتهای صف قرار میدهد و dequeue اولین المان صف را بیرون میکشد.
پیاده سازی صف به صورت پویا با لیستهای پیوندی
برای پیاده سازی صف، لیستهای پیوندی یک طرفه کافی هستند:
در این حالت عنصر جدید مثل سابق به انتهای لیست اضافه میشود و برای حذف هم که از اول لیست کمک میگیریم و با حذف عنصر اول، متغیر Head به عنصر یا المان دوم اشاره خواهد کرد.
کلاس از پیش آمده صف در دات نت <Queue<T است و نحوهی استفاده آن بدین شکل است:
static void Main() { Queue<string> queue = new Queue<string>(); queue.Enqueue("Message One"); queue.Enqueue("Message Two"); queue.Enqueue("Message Three"); queue.Enqueue("Message Four"); while (queue.Count > 0) { string msg = queue.Dequeue(); Console.WriteLine(msg); } } //خروجی //Message One //Message Two //Message Thre //Message Four
10) Practical .NET: Powerful JavaScript With Upshot and Knockout
The Microsoft JavaScript Upshot library provides a simplified API for
retrieving data from the server and caching it at the client for reuse. Coupled
with Knockout, the two JavaScript libraries form the pillars of the Microsoft
client-side programming model.
9) On VB: Database Synchronization with the Microsoft Sync
Framework
The Microsoft Sync Framework is a highly flexible framework
for synchronizing files and data between a client and a master data store. With
great flexibility often comes complexity and confusion, however.
8) C# Corner: Performance Tips for Asynchronous Development in
C#
Visual Studio Async is a powerful development framework, but it's
important to understand how it works to avoid performance hits.
7) 2 Great JavaScript Data-Binding Libraries
JavaScript
libraries help you build powerful, data-driven HTML5 apps.
6) On VB: Entity Framework Code-First Migrations
Code First
Migrations allow for database changes to be implemented all through code.
Through the use of Package Manager Console (PMC), commands can be used to
scaffold database changes.
5) C# Corner: The New Read-Only Collections in .NET 4.5
Some
practical uses for the long-awaited interfaces, IReadOnlyList and
IReadOnlyDictionary, in .NET Framework 4.5.
4) C# Corner: Building a Windows 8 RSS Reader
Eric Vogel
walks through a soup-to-nuts demo for building a Metro-style RSS reader.
3) C# Corner: The Build Pattern in .NET
How to separate
complex object construction from its representation using the Builder design
pattern in C#.
2) Inside Visual Studio 11: A Guided Tour
Visual Studio 2012
(code-named Visual Studio 11 then) is packed with new features to help you be a
more efficient, productive developer. Here's your guided tour.
1) HTML5 for ASP.NET Developers
The technologies bundled as
HTML5 finally support what developers have been trying to get HTML to do for
decades.
ASP.NET MVC #7
آشنایی با Razor Views
قبل از اینکه بحث جاری ASP.NET MVC را بتوانیم ادامه دهیم و مثلا مباحث دریافت اطلاعات از کاربر، کار با فرمها و امثال آنرا بررسی کنیم، نیاز است حداقل به دستور زبان یکی از View Engineهای ASP.NET MVC آشنا باشیم.
MVC3 موتور View جدیدی را به نام Razor معرفی کرده است که به عنوان روش برگزیده ایجاد Viewها در این سیستم به شمار میرود و فوق العاده نسبت به ASPX view engine سابق، زیباتر، سادهتر و فشردهتر طراحی شده است و یکی از اهداف آن تلفیق code و markup میباشد. در این حالت دیگر پسوند فایلهای Viewها همانند سابق ASPX نخواهد بود و به cshtml و یا vbhtml تغییر یافته است. همچنین برخلاف web forms view engine از System.Web.Page مشتق نشده است. و باید دقت داشت که Razor یک زبان برنامه نویسی جدید نیست. در اینجا از مخلوط زبانهای سی شارپ و یا ویژوال بیسیک به همراه تگهای html استفاده میشود.
البته این را هم باید عنوان کرد که این مسایل سلیقهای است. اگر با web forms view engine راحت هستید، با همان کار کنید. اگر با هیچکدام از اینها راحت نیستید (!) نمونههای دیگر هم وجود دارند، مثلا:
Razor Views یک سری قابلیت جالب را هم به همراه دارند:
1) امکان کامپایل آنها به درون یک DLL وجود دارد. مزیت: استفاده مجدد از کد، عدم نیاز به وجود صریح فایل cshtml یا vbhtml بر روی دیسک سخت.
2) آزمون پذیری: از آنجائیکه Razor viewها به صورت یک کلاس کامپایل میشوند و همچنین از System.Web.Page مشتق نخواهند شد، امکان بررسی HTML نهایی تولیدی آنهابدون نیاز به راه اندازی یک وب سرور وجود دارد.
3) IntelliSense ویژوال استودیو به خوبی آنرا پوشش میدهد.
4) با توجه به مواردی که ذکر شد، یک اتفاق جالب هم رخ داده است: امکان استفاده از Razor engine خارج از ASP.NET MVC هم وجود دارد. برای مثال یک سرویس ویندوز NT طراحی کردهاید که قرار است ایمیل فرمت شدهای به همراه اطلاعات مدلهای شما را در فواصل زمانی مشخص ارسال کند؟ میتوانید برای طراحی آن از Razor engine استفاده کنید و تهیه خروجی نهایی HTML آن نیازی به راه اندازی وب سرور و وهله سازی HttpContext ندارد.
ساختار پروژه مثال جاری
در ادامه مرور سریعی خواهیم داشت بر دستور زبان Razor engine و جهت نمایش این قابلیتها، یک مثال ساده را در ابتدا با مشخصات زیر ایجاد خواهیم کرد:
الف) یک empty ASP.NET MVC 3 project را ایجاد کنید و نوع View engine را هم در ابتدای کار Razor انتخاب نمائید.
ب) دو کلاس زیر را به پوشه مدلهای برنامه اضافه کنید:
namespace MvcApplication3.Models
{
public class Product
{
public Product(string productNumber, string name, decimal price)
{
Name = name;
Price = price;
ProductNumber = productNumber;
}
public string ProductNumber { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
using System.Collections.Generic;
namespace MvcApplication3.Models
{
public class Products : List<Product>
{
public Products()
{
this.Add(new Product("D123", "Super Fast Bike", 1000M));
this.Add(new Product("A356", "Durable Helmet", 123.45M));
this.Add(new Product("M924", "Soft Bike Seat", 34.99M));
}
}
}
کلاس Products صرفا یک منبع داده تشکیل شده در حافظه است. بدیهی است هر نوع ORM ایی که یک ToList را بتواند در اختیار شما قرار دهد، توانایی تشکیل لیست جنریکی از محصولات را نیز خواهد داشت و تفاوتی نمیکند که کدامیک مورد استفاده قرار گیرد.
ج) سپس یک کنترلر جدید به نام ProductsController را به پوشه Controllers برنامه اضافه میکنیم:
using System.Web.Mvc;
using MvcApplication3.Models;
namespace MvcApplication3.Controllers
{
public class ProductsController : Controller
{
public ActionResult Index()
{
var products = new Products();
return View(products);
}
}
}
د) بر روی نام متد Index کلیک راست کرده، گزینه Add view را جهت افزودن View متناظر آن، انتخاب کنید. البته میشود همانند قسمت پنجم گزینه Create a strongly typed view را انتخاب کرد و سپس Product را به عنوان کلاس مدل انتخاب نمود و در آخر خیلی سریع یک لیست از محصولات را نمایش داد، اما فعلا از این قسمت صرفنظر نمائید، چون میخواهیم آن را دستی ایجاد کرده و توضیحات و نکات بیشتری را بررسی کنیم.
ه) برای اینکه حین اجرای برنامه در VS.NET هربار نخواهیم که آدرس کنترلر Products را دستی در مرورگر وارد کنیم، فایل Global.asax.cs را گشوده و سپس در متد RegisterRoutes، در سطر Parameter defaults، مقدار پیش فرض کنترلر را مساوی Products قرار دهید.
مرجع سریع Razor
ابتدا کدهای View متد Index را به شکل زیر وارد نمائید:
@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>
@:line-2: @data <br />
<text>line-3:</text> @data
}
<br />
site@(data)
<br />
@@name
<br />
@(number/10)
<br />
First product: @Model.First().Name
<br />
@if (@number>10)
{
<span>@data</span>
}
else
{
<text>Plain Text</text>
}
<br />
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}
@*
A Razor Comment
*@
<br />
@("First product: " + Model.First().Name)
<br />
<img src="@(number).jpg" />
در ادامه توضیحات مرتبط با این کدها ارائه خواهد شد:
1) نحوه معرفی یک قطعه کد
@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>
@:line-2: @data <br />
<text>line-3:</text> @data
}
این کدها متعلق به Viewایی است که در قسمت (د) بررسی ساختار پروژه مثال جاری، ایجاد کردیم. در ابتدای آن هم نوع model مشخص شده تا بتوان سادهتر به اطلاعات شیء Model به کمک IntelliSense دسترسی داشت.
برای ایجاد یک قطعه کد در Viewایی از نوع Razor به این نحو عمل میشود:
@{ ...Code Block.... }
در اینجا مجاز هستیم کدهای سی شارپ را وارد کنیم. یک نکته جالب را هم باید درنظر داشت: امکان نوشتن تگهای html هم در این بین وجود دارد (بدون اینکه مجبور باشیم قطعه کد شروع شده را خاتمه دهیم، به حالت html معمولی سوئیچ کرده و دوباره یک قطعه کد دیگر را شروع نمائیم). مانند line1 مثال فوق. اگر کمی پایینتر از این سطر مثلا بنویسیم line2 (به عنوان یک برچسب) کامپایلر ایراد خواهد گرفت، زیرا این مورد نه متغیر است و نه از پیش تعریف شده است. به عبارتی نباید فراموش کنیم که اینجا قرار است کد نوشته شود. برای رفع این مشکل دو راه حل وجود دارد که در سطرهای دو و سه ملاحظه میکنید. یا باید از تگی به نام text برای معرفی یک برچسب در این میان استفاده کرد (سطر سه) یا اگر قرار است اطلاعاتی به شکل یک متن معمولی پردازش شود ابتدای آن مانند سطر دوم باید یک @: قرار گیرد.
کمی پایینتر از قطعه کد معرفی شده در بالا بنویسید:
<br />
site@data
اکنون اگر خروجی این View را در مرورگر بررسی کنید، دقیقا همین site@data خواهد بود. چون در این حالت Razor تصور خواهد کرد که قصد داشتهاید یک آدرس ایمیل را وارد کنید. برای این حالت خاص باید نوشت:
<br />
site@(data)
به این ترتیب data از متغیر data تعریف شده در code block قبلی برنامه دریافت و نمایش داده خواهد شد.
شبیه به همین حالت در مثال زیر هم وجود دارد:
<img src="@(number).jpg" />
در اینجا اگر پرانتزها را حذف کنیم، Razor فرض را بر این خواهد گذاشت که شیء number دارای خاصیت jpg است. بنابراین باید به نحو صریحی، بازه کاری را مشخص نمائیم.
بکار گیری این علامت @ یک نکته جنبی دیگر را هم به همراه دارد. فرض کنید در صفحه قصد دارید آدرس توئیتری شخصی را وارد کنید. مثلا:
<br />
@name
در این حالت View کامپایل نخواهد شد و Razor تصور خواهد کرد که قرار است اطلاعات متغیری به نام name را نمایش دهید. برای نمایش این اطلاعات به همین شکل، یک @ دیگر به ابتدای سطر اضافه کنید:
<br />
@@name
2) نحوه معرفی عبارات
عبارات پس از علامت @ معرفی میشوند و به صورت پیش فرض Html Encoded هستند (در قسمت 5 در اینباره بیشتر توضیح داده شد):
First product: @Model.First().Name
در این مثال با توجه به اینکه نوع مدل در ابتدای View مشخص شده است، شیء Model به لیستی از Products اشاره میکند.
یک نکته:
مشخص سازی حد و مرز صریح یک متغیر در مثال زیر نیز کاربرد دارد:
<br />
@number/10
اگر خروجی این مثال را بررسی کنید مساوی 12/10 خواهد بود و محاسبهای انجام نخواهد شد. برای حل این مشکل باز هم از پرانتز میتوان کمک گرفت:
<br />
@(number/10)
@if (@number>10)
{
<span>@data</span>
}
else
{
<text>Plain Text</text>
}
یک عبارت شرطی در اینجا با @if شروع میشود و سپس نکاتی که در «نحوه معرفی یک قطعه کد» بیان شد، در مورد عبارات داخل {} صادق خواهد بود. یعنی در اینجا نیز میتوان عبارات سی شارپ مخلوط با تگهای html را نوشت.
یک نکته: عبارت شرطی زیر نادرست است. حتما باید سطرهای کدهای سی شارپ بین {} محصور شوند؛ حتی اگر یک سطر باشند:
@if( i < 1 ) int myVar=0;
4) نحوه استفاده از حلقه foreach
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}
حلقه foreach نیز مانند عبارات شرطی با یک @ شروع شده و داخل {} بدنه آن نکات «نحوه معرفی یک قطعه کد» برقرار هستند (امکان تلفیق code و markup با هم).
کسانی که پیشتر با web forms کار کرده باشند، احتمالا الان خواهند گفت که این یک پس رفت است و بازگشت به دوران ASP کلاسیک دهه نود! ما به ندرت داخل صفحات aspx وب فرمها کد مینوشتیم. مثلا پیشتر یک GridView وجود داشت و یک دیتاسورس که به آن متصل میشد؛ مابقی خودکار بود و ما هیچ وقت حلقهای ننوشتیم. در اینجا هم این مساله با نوشتن برای مثال «html helpers» قابل کنترل است که در قسمتهای بعدی به آن پرداخته خواهد شد. به عبارتی قرار نیست به این نحو با Viewهای Razor رفتار کنیم. این قسمت فقط یک آشنایی کلی با Syntax است.
5) امکان تعریف فضای نام در ابتدای View
@using namespace;
6) نحوه نوشتن توضیحات سمت سرور:
@*
A Razor Comment / Server side Comment
*@
7) نحوه معرفی عبارات چند جزئی:
@("First product: " + Model.First().Name)
همانطور که ملاحظه میکنید، ذکر یک پرانتز برای معرفی عبارات چندجزئی کفایت میکند.
استفاده از موتور Razor خارج از ASP.NET MVC
پیشتر مطلبی را در مورد «تهیه قالب برای ایمیلهای ارسالی یک برنامه ASP.Net» در این سایت مطالعه کردهاید. اولین سؤالی هم که در ذیل آن مطلب مطرح شده این است: «در برنامههای ویندوز چطور؟» پاسخ این است که کل آن مثال بر مبنای HttpContext.Current.Server.Execute کار میکند. یعنی باید مراحل وهله سازی HttpContext و شیء Server توسط یک وب سرور و درخواست رسیده طی شود و ... شبیه سازی آن آنچنان مرسوم و کار سادهای نیست.
اما این مشکل با Razor وجود ندارد. به عبارتی در اینجا برای رندر کردن یک Razor View به html نهایی، نیازی به HttpContext نیست. بنابراین از این امکانات مثلا در یک سرویس ویندوز ان تی یا یک برنامه کنسول، WinForms، WPF و غیره هم میتوان استفاده کرد.
برای اینکه بتوان از Razor خارج از ASP.NET MVC استفاده کرد، نیاز به اندکی کدنویسی هست مثلا استفاده از کامپایلر سی شارپ یا وی بی و کامپایل پویای کد و یک سری ست آپ دیگر. پروژهای به نام RazorEngine این کپسوله سازی رو انجام داده و از اینجا http://razorengine.codeplex.com/ قابل دریافت است.
مجوز استفاده از فایلهای جاوا اسکریپتی آن MIT است؛ به این معنا که در هر نوع پروژهای قابل استفاده است. مجوز استفاده از کامپوننتهای سمت سرور آن که برای نمونه جهت ASP.NET MVC یک سری HTML Helper را تدارک دیدهاند، تجاری میباشد. در ادامه قصد داریم صرفا از فایلهای JS عمومی آن استفاده کنیم.
دریافت jqGrid
برای دریافت jqGrid میتوانید به مخزن کد آن، در آدرس https://github.com/tonytomov/jqGrid/releases و یا از طریق NuGet اقدام کنید:
PM> Install-Package Trirand.jqGrid
از jQuery UI برای تولید صفحات جستجوی بر روی رکوردها و همچنین تولید خودکار صفحات ویرایش و یا افزودن رکوردها استفاده میکند. به علاوه آیکنها، قالب و رنگ خود را نیز از jQuery UI دریافت میکند. بنابراین اگر قصد تغییر قالب آنرا داشتید تنها کافی است یک قالب استاندارد دیگر jQuery UI را مورد استفاده قرار دهید.
تنظیمات اولیه فایل Layout سایت
پس از دریافت بستهی نیوگت jqGrid، نیاز است فایلهای مورد نیاز اصلی آنرا به شکل زیر به فایل layout پروژه اضافه کرد:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - My ASP.NET Application</title> <link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" /> <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" /> <link href="~/Content/Site.css" rel="stylesheet" type="text/css" /> </head> <body> <div> @RenderBody() </div> <script src="~/Scripts/jquery-1.7.2.min.js"></script> <script src="~/Scripts/jquery-ui-1.8.11.min.js"></script> <script src="~/Scripts/i18n/grid.locale-fa.js"></script> <script src="~/Scripts/jquery.jqGrid.min.js"></script> @RenderSection("Scripts", required: false) </body> </html>
این گرید به همراه فایل زبان فارسی grid.locale-fa.js نیز میباشد که در کدهای فوق پیوست شدهاست. البته اگر فرصت کردید نیاز است کمی ترجمههای آن بهبود پیدا کنند.
تنظیمات ثانویه site.css
.ui-widget { } /*how to move jQuery dialog close (X) button from right to left*/ .ui-jqgrid .ui-jqgrid-caption-rtl { text-align: center !important; } .ui-dialog .ui-dialog-titlebar-close { left: .3em !important; } .ui-dialog .ui-dialog-title { margin: .1em 0 .1em .8em !important; direction: rtl !important; float: right !important; }
همچنین محل قرار گیری دکمهی بسته شدن دیالوگها و راست به چپ کردن عناوین آنها نیز در اینجا قید شدهاند.
مدل برنامه
در ادامه قصد داریم لیستی از محصولات را با ساختار ذیل، توسط jqGrid نمایش دهیم:
namespace jqGrid01.Models { public class Product { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsAvailable { set; get; } } }
ساختار دادهای مورد نیاز توسط jqGrid
jqGrid مستقل است از فناوری سمت سرور. بنابراین هر چند در عنوان بحث ASP.NET MVC ذکر شدهاست، اما از ASP.NET MVC صرفا جهت بازگرداندن خروجی JSON استفاده خواهیم کرد و این مورد در هر فناوری سمت سرور دیگری نیز میتواند انجام شود.
using System.Collections.Generic; namespace jqGrid01.Models { public class JqGridData { public int Total { get; set; } public int Page { get; set; } public int Records { get; set; } public IList<JqGridRowData> Rows { get; set; } public object UserData { get; set; } } public class JqGridRowData { public int Id { set; get; } public IList<string> RowCells { set; get; } } }
Total، نمایانگر تعداد صفحات اطلاعات است. عدد Page، شماره صفحهی جاری است. عدد Records، تعداد کل رکوردهای گزارش را مشخص میکند. ساختار ردیفهای آن نیز تشکیل شدهاست از یک Id به همراه سلولهایی که باید با فرمت string، بازگشت داده شوند.
UserData اختیاری است. برای مثال اگر خواستید جمع کل صفحه را در ذیل گرید نمایش دهید، میتوانید یک anonymous object را در اینجا مقدار دهی کنید. خاصیتهای آن دقیقا باید با نام خاصیتهای ستونهای متناظر، یکی باشند. برای مثال اگر میخواهید عددی را در ستون Id، در فوتر گرید نمایش دهید، باید نام خاصیت را Id ذکر کنید.
کدهای سمت کلاینت گرید
در اینجا کدهای کامل سمت کلاینت گرید را ملاحظه میکنید:
@{ ViewBag.Title = "Index"; } <div dir="rtl" align="center"> <div id="rsperror"></div> <table id="list" cellpadding="0" cellspacing="0"></table> <div id="pager" style="text-align:center;"></div> </div> @section Scripts { <script type="text/javascript"> $(document).ready(function () { $('#list').jqGrid({ caption: "آزمایش اول", //url from wich data should be requested url: '@Url.Action("GetProducts","Home")', //type of data datatype: 'json', jsonReader: { root: "Rows", page: "Page", total: "Total", records: "Records", repeatitems: true, userdata: "UserData", id: "Id", cell: "RowCells" }, //url access method type mtype: 'GET', //columns names colNames: ['شماره', 'نام محصول', 'موجود است', 'قیمت'], //columns model colModel: [ { name: 'Id', index: 'Id', align: 'right', width: 50, sorttype: "number" }, { name: 'Name', index: 'Name', align: 'right', width: 300 }, { name: 'IsAvailable', index: 'IsAvailable', align: 'center', width: 100, formatter: 'checkbox' }, { name: 'Price', index: 'Price', align: 'center', width: 100, sorttype: "number" } ], //pager for grid pager: $('#pager'), //number of rows per page rowNum: 10, rowList: [10, 20, 50, 100], //initial sorting column sortname: 'Id', //initial sorting direction sortorder: 'asc', //we want to display total records count viewrecords: true, altRows: true, shrinkToFit: true, width: 'auto', height: 'auto', hidegrid: false, direction: "rtl", gridview: true, rownumbers: true, footerrow: true, userDataOnFooter: true, loadComplete: function() { //change alternate rows color $("tr.jqgrow:odd").css("background", "#E0E0E0"); }, loadError: function(xhr, st, err) { jQuery("#rsperror").html("Type: " + st + "; Response: " + xhr.status + " " + xhr.statusText); } //, loadonce: true }) .jqGrid('navGrid', "#pager", { edit: false, add: false, del: false, search: false, refresh: true }) .jqGrid('navButtonAdd', '#pager', { caption: "تنظیم نمایش ستونها", title: "Reorder Columns", onClickButton: function() { jQuery("#list").jqGrid('columnChooser'); } }); }); </script> }
Div سومی با id مساوی rsperror نیز تعریف شدهاست که از آن جهت نمایش خطاهای بازگشت داده شده از سرور استفاده کردهایم.
- در ادامه نحوهی فراخوانی افزونهی jqGrid را بر روی جدول list ملاحظه میکنید.
- خاصیت caption، عنوان نمایش داده شده در بالای گرید را مقدار دهی میکند:
- خاصیت url، به آدرسی اشاره میکند که قرار است ساختار JqGridData ایی را که پیشتر در مورد آن بحث کردیم، با فرمت JSON بازگشت دهد. در اینجا برای مثال به یک اکشن متد کنترلری در یک پروژهی ASP.NET MVC اشاره میکند.
- datatype را برابر json قرار دادهایم. از نوع xml نیز پشتیبانی میکند.
- شیء jsonReader را از این جهت مقدار دهی کردهایم تا بتوانیم شیء JqGridData را با اصول نامگذاری دات نت، هماهنگ کنیم. برای درک بهتر این موضوع، فایل jquery.jqGrid.src.js را باز کنید و در آن به دنبال تعریف jsonReader بگردید. به یک چنین مقادیر پیش فرضی خواهید رسید:
ts.p.jsonReader = $.extend(true,{ root: "rows", page: "page", total: "total", records: "records", repeatitems: true, cell: "cell", id: "id", userdata: "userdata", subgrid: {root:"rows", repeatitems: true, cell:"cell"} },ts.p.jsonReader);
- در ادامه mtype به GET تنظیم شدهاست. در اینجا مشخص میکنیم که عملیات Ajax ایی دریافت اطلاعات از سرور توسط GET انجام شود یا برای مثال توسط POST.
- خاصیت colNames، معرف نام ستونهای گرید است. برای اینکه این نامها از راست به چپ نمایش داده شوند، باید خاصیت direction به rtl تنظیم شود.
- colModel آرایهای است که تعاریف ستونها را در بر دارد. مقدار name آن باید یک نام منحصربفرد باشد. از این نام در حین جستجو یا ویرایش اطلاعات استفاده میشود. مقدار index نامی است که جهت مرتب سازی اطلاعات، به سرور ارسال میشود. تنظیم sorttype در اینجا مشخص میکند که آیا به صورت پیش فرض، ستون جاری رشتهای مرتب شود یا اینکه برای مثال عددی پردازش گردد. مقادیر مجاز آن text (مقدار پیش فرض)، float، number، currency، numeric، int ، integer، date و datetime هستند.
- در ستون IsAvailable، مقدار formatter نیز تنظیم شدهاست. در اینجا توسط formatter، نوع bool دریافتی با یک checkbox نمایش داده خواهد شد.
- خاصیت pager به id متناظری در صفحه اشاره میکند.
- توسط rowNum مشخص میکنیم که در هر صفحه چه تعداد رکورد باید نمایش داده شوند.
- تعداد رکوردهای نمایش داده شده را میتوان توسط rowList پویا کرد. در اینجا آرایهای را ملاحظه میکنید که توسط اعداد آن، کاربر امکان انتخاب صفحاتی مثلا 100 ردیفه را نیز پیدا میکند. rowList به صورت یک dropdown در کنار عناصر راهبری صفحه در فوتر گرید ظاهر میشود.
- خاصیت sortname، نحوهی مرتب سازی اولیه گرید را مشخص میکند.
- خاصیت sortorder، جهت مرتب سازی اولیهی گردید را تنظیم میکند.
- viewrecords: تعداد رکوردها را در نوار ابزار پایین گرید نمایش میدهد.
- altRows: سبب میشود رنگ متن ردیفها یک در میان متفاوت باشد.
- shrinkToFit: به معنای تنظیم خودکار اندازهی سلولها بر اساس اندازهی دادهای است که دریافت میکنند.
- width: عرض گرید، که در اینجا به auto تنظیم شدهاست.
- height: طول گرید، که در اینجا به auto جهت محاسبهی خودکار، تنظیم شدهاست.
- gridview: برای بالا بردن سرعت نمایشی به true تنظیم شدهاست. در این حالت کل ردیف یکباره درج میشود. اگر از subgird یا حالت نمایش درختی استفاده شود، باید این خاصیت را false کرد.
- rownumbers: ستون سمت راست شماره ردیفهای خودکار را نمایش میدهد.
- footerrow: سبب نمایش ردیف فوتر میشود.
- userDataOnFooter: سبب خواهد شد تا خاصیت UserData مقدار دهی شده، در ردیف فوتر ظاهر شود.
- loadComplete : یک callback است که زمان پایان بارگذاری صفحهی جاری را مشخص میکند. در اینجا با استفاده از jQuery سبب شدهایم تا رنگ پس زمینهی ردیفها یک در میان تغییر کند.
- loadError: اگر از سمت سرور خطایی صادر شود، در این callback قابل دریافت خواهد بود.
- در ادامه توسط فراخوانی متد jqGrid با پارامتر navGrid، در ناحیه pager سبب نمایش دکمه refresh شدهایم. این دکمه سبب بارگذاری مجدد اطلاعات گردید از سرور میشود.
- همچنین به کمک متد jqGrid با پارامتر navButtonAdd در ناحیه pager، سبب نمایش دکمهای که صفحهی انتخاب ستونها را ظاهر میکند، خواهیم شد.
پیشنیاز کدهای سمت سرور jqGrid
اگر به تنظیمات گرید دقت کرده باشید، خاصیت index ستونها، نامی است که به سرور، جهت اطلاع رسانی در مورد فیلتر اطلاعات و مرتب سازی مجدد آنها ارسال میگردد. این نام، بر اساس کلیک کاربر بر روی ستونهای موجود، هر بار میتوان متفاوت باشد. بنابراین بجای if و else نوشتنهای طولانی جهت مرتب سازی اطلاعات، میتوان از کتابخانهی معروفی به نام dynamic LINQ استفاده کرد.
PM> Install-Package DynamicQuery
کدهای سمت سرور بازگشت اطلاعات به فرمت JSON
در کدهای سمت کلاینت، به اکشن متد GetProducts اشاره شده بود. تعاریف کامل آنرا در ذیل مشاهده میکنید:
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Web.Mvc; using jqGrid01.Models; using jqGrid01.Extensions; // for dynamic OrderBy namespace jqGrid01.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult GetProducts(string sidx, string sord, int page, int rows) { var list = ProductDataSource.LatestProducts; var pageIndex = page - 1; var pageSize = rows; var totalRecords = list.Count; var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize); var products = list.AsQueryable() .OrderBy(sidx + " " + sord) .Skip(pageIndex * pageSize) .Take(pageSize) .ToList(); var jqGridData = new JqGridData { UserData = new // نمایش در فوتر { Name = "جمع صفحه", Price = products.Sum(x => x.Price) }, Total = totalPages, Page = page, Records = totalRecords, Rows = (products.Select(product => new JqGridRowData { Id = product.Id, RowCells = new List<string> { product.Id.ToString(CultureInfo.InvariantCulture), product.Name, product.IsAvailable.ToString(), product.Price.ToString(CultureInfo.InvariantCulture) } })).ToList() }; return Json(jqGridData, JsonRequestBehavior.AllowGet); } } }
- امضای متد GetProducts نیز مهم است. دقیقا همین پارامترها با همین نامها از طرف jqGrid به سرور ارسال میشوند که توسط آنها ستون مرتب سازی، جهت مرتب سازی، صفحهی جاری و تعداد ردیفی که باید بازگشت داده شوند، قابل دریافت است.
- در این کدها دو قسمت مهم وجود دارند:
الف) متد OrderBy نوشته شده، به صورت پویا عمل میکند و از کتابخانهی Dynamic LINQ مایکروسافت بهره میبرد.
به علاوه توسط Take و Skip کار صفحه بندی و بازگشت تنها بازهای از اطلاعات مورد نیاز، انجام میشود.
ب) لیست جنریک محصولات، در نهایت باید با فرمت JqGridData به صورت JSON بازگشت داده شود. نحوهی این Projection را در اینجا میتوانید ملاحظه کنید.
هر ردیف این لیست، باید تبدیل شود به ردیفی از جنس JqGridRowData، تا توسط jqGrid قابل پردازش گردد.
- توسط مقدار دهی UserData، برچسبی را در ذیل ستون Name و مقداری را در ذیل ستون Price نمایش خواهیم داد.
برای مطالعهی بیشتر
بهترین راهنمای جزئیات این Grid، مستندات آنلاین آن هستند: http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs
همچنین این مستندات را با فرمت PDF نیز میتوانید مطالعه کنید: http://www.trirand.com/blog/jqgrid/downloads/jqgriddocs.pdf
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid01.zip
مثالهای سری jqGrid تغییرات زیادی داشتند. برای دریافت آنها به این مخزن کد مراجعه کنید.
ایجاد گزارش با داده های ثابت و متغیر
public class Rpt { public string Title {set;get;} public int Price {set;get;} }
var list = new List<Rpt>(); list.Add(new Rpt{ Title = "عنوان دلخواه", Price = 1});
پس از ساخت لیست، از منبع داده StronglyTypedList استفاده کنید. یک مثال از این منبع داده
ASP.NET MVC #1
نکته ای که برای من خیلی جالبه وقتی با وب فرم کار می کنی html جاوا جی کوئری مثل عقب مونده ها نگات می کنند و یه دایر دورت می کشن که از اون بیرون نری ولی وقتی با mvc کاری می کنی احساس می کنی دنیای وب رو در اختیار توست تا زیباترین و قویترین وب اپلیکیشن رو طراحی کنی !