بیایید فرض کنیم که شما، ناشر این اسمبلی جدید با رفع باگهای اسمبلی قبلی هستید. موقعیکه شما یک اسمبلی جدید را توزیع میکنید، به همراه آن فایل پیکربندی جدیدی را تشکیل داده و ارسال میکنید. این فایل پیکربندی جدید دقیقا شبیه به همان فایل پیکربندی است که با آن آشنا شدیم، منتها همنام اسمبلی مورد نظر میباشد؛ مثل dotnettips.config برای dotnettips.dll.
<configuration> <runtime> <assemblyBinding xmlns="urn:schemasmicrosoftcom:asm.v1"> <dependentAssembly> <assemblyIdentity name="SomeClassLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/> <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" /> <codeBase version="2.0.0.0" href="http://www.Wintellect.com/SomeClassLibrary.dll"/> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
AL.exe /out:Policy.1.0.SomeClassLibrary.dll /version:1.0.0.0 /keyfile:MyCompany.snk /linkresource:SomeClassLibrary.config
سوئییچ out باعث ساخت فایل PE با نام مشخص شده میباشد که تنها شامل یک manifest میشود و شامل 5 بخش میشود:
بخش اول شامل عبارت policy است تا بگویید که این اسمبلی شامل یک فایل اطلاعاتی درباره سیاستهای تعیین شده است. بخش دوم و سوم مربوط به ورژن اسمبلی است که به ترتیب major و minor آن مشخص میشوند و این سیاستها فقط روی این نسخهها اتفاق میافتد. ادامه شماره نسخهها چون revision number و ... اهمیتی ندارد و فقط همان دو مقدار اول شرط اصلی هستند. بخش چهارم هم شامل نام اسمبلی مربوطه میشود و بخش پنجم هم پسوند اسمبلی است.
سوئیچ linkresource هم میگوید که فایل پیکربندی قرار است جدای از اسمبلی باشد و ناشر باید این فایل را به همراه نسخهی جدید اسمبلی ارسال و توزیع کند. شما نمیتوانید با استفاده از سوئیچ EmbedResource فایل پیکربندی را داخل اسمبلی قرار دهید؛ چون CLR انتظار دارد این فایل جدا باشد. (در مورد این سوئیچها در قسمت شانزدهم توضیح دادهایم)
<publisherPolicy apply="no"/>
پایان فصل سوم و بخش اول
> ng test --help
یک مثال: بررسی اجرای دستور ng test
یکی از مثالهای بررسی شدهی در این سری را انتخاب و یا حتی یک برنامهی جدید را توسط Angular CLI ایجاد کرده و سپس دستور ng test را در ریشهی این پروژه اجرا کنید. به این ترتیب برنامه به صورت خودکار کامپایل شده و سپس به صورت خودکار آزمونهای واحد آنرا که در فایلهای spec.ts.* قرار دارند، اجرا میکند. در آخر نتیجه را در مرورگر گزارش میدهد:
همانطور که مشخص است، 3 specs, 3 failures داریم. در اینجا میتوان بر روی لینک Spec List کلیک کرد و لیست آزمونهای واحد موجود را مشاهده نمود:
هر کدام از عناوین ذکر شده نیز به جزئیات مشکلات آنها، لینک شدهاند. برای مثال اگر بر روی اولین مورد کلیک کنیم، خطایی مانند «'alert' is not a known element» قابل مشاهدهاست. به این معنا که برای نمونه در قسمت قبل کامپوننت alert را به صفحه اضافه کردیم:
<alert type="success">Alert success!</alert>
import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); }));
پس از این تغییر، بلافاصله مجدد برنامه کامپایل شده و آزمونهای واحد آن با موفقیت اجرا میشوند (با این فرض که هنوز پنجرهی اجرا کنندهی دستور ng test را باز نگه داشتهاید):
تغییر افزودن schemas: [NO_ERRORS_SCHEMA] را باید در مورد تمام فایلهای spec موجود تکرار کرد.
گزینههای مختلف دستور ng test
دستور ng test به همراه گزینههای متعددی است که شرح آنها را در جدول ذیل مشاهده میکنید:
گزینه | مخفف | توضیح |
code-coverage-- | cc- | تولید گزارش code coverage که به صورت پیش فرض خاموش است. |
colors-- | به صورت پیش فرض فعال است و سبب نمایش رنگهای قرمز و سبز، برای آزمونهای شکست خورده و یا موفق میشود. | |
single-run-- | sr- | اجرای یکبارهی آزمونهای واحد، بدون فعال سازی گزینهی مشاهدهی مداوم تغییرات که به صورت پیش فرض خاموش است. |
progress-- | نمایش جزئیات کامپایل و اجرای آزمونهای واحد که به صورت پیش فرض فعال است. | |
sourcemaps-- | sm- | تولید فایلهای سورسمپ که به صورت پیش فرض فعال است. |
watch-- | w- | بررسی مداوم تغییرات فایلها و اجرای آزمونهای واحد به صورت خودکار که به صورت پیش فرض فعال است. |
بنابراین اجرا دستور ng test بدون ذکر هیچ گزینهای به معنای اجرای مداوم آزمونهای واحد، در صورت مشاهدهی تغییراتی در آنها، به کمک Karma است.
همچنین دو دستور ذیل نیز به یک معنا هستند و هر دو سبب یکبار اجرای آزمونهای واحد میشوند:
> ng test -sr > ng test -w false
اجرای بررسی میزان پوشش آزمونهای واحد
یکی از گزینههای ng test روشن کردن پرچم code-coverage است:
> ng test --code-coverage
> ng test -sr -cc
کار این آزمون بررسی قسمتهای مختلف برنامه و ارائه گزارشی است که مشخص میکند آیا آزمونهای واحد نوشته شده تمام انشعابات برنامه را پوشش میدهند یا خیر؟ برای مثال اگر در متدی if/else دارید، آیا فقط قسمت if را پوشش دادهاید و یا آیا قسمت else هم در آزمونهای واحد، بررسی شدهاست.
اجرای آزمونهای end to end
هدف از ساخت یک برنامه ... استفادهی از آن توسط دیگران است؛ اینجا است که آزمونهای end to end مفهوم پیدا میکنند. در آزمونهای e2e رفتار برنامه همانند حالتی که یک کاربر از آن استفاده میکند، بررسی میشود (برای مثال باز کردن مرورگر، لاگین و مرور صفحات). برای این منظور، Angular CLI در پشت صحنه از Protractor برای این نوع آزمونها استفاده میکند.
برای مشاهدهی راهنما و گزینههای مختلف مرتبط با آزمونهای e2e، میتوان دستور ذیل را صادر کرد:
>ng e2e --help
گزینه | مخفف | توضیح |
config-- | c- | به فایل کانفیگ آزمونهای e2e اشاره میکند که به صورت پیشفرض همان protractor.conf.js واقع در ریشهی پروژهاست. |
element-explorer-- | ee- | بررسی و دیباگ protractor از طریق خط فرمان |
serve-- | s- | کامپایل و توزیع برنامه بر روی پورتی اتفاقی (حالت پیش فرض آن true است) |
specs-- | sp- | پیش فرض آن بررسی تمام specهای موجود در پروژهاست. اگر نیاز به لغو آن باشد میتوان از این گزینه استفاده کرد. |
webdriver-update-- | wu- |
به روز رسانی web driver که به صورت پیش فرض فعال است. |
بنابراین زمانیکه دستور ng e2e صادر میشود، به معنای کامپایل، توزیع برنامه بر روی پورتی اتفاقی و اجرای آزمونها است.
از این جهت که این نوع آزمونها، وابستهی به جزئی خاص از برنامه نیستند، حالت عمومی داشته و فایلهای spec آنها را میتوان در پوشهی e2e واقع در ریشهی پروژه، یافت. برای مثال در قسمتی از آن کار یافتن متن نمایش داده شدهی در صفحهی اول سایت انجام میشود
getParagraphText() { return element(by.css('app-root h1')).getText(); }
expect(page.getParagraphText()).toEqual('app works!');
برای آزمایش آن دستور ng e2e را در ریشهی پروژه صادر کنید. همچنین دقت داشته باشید که در این حالت نیاز است به اینترنت نیز متصل باشد؛ چون از chromedriver api گوگل نیز استفاده میکند. در غیراینصورت خطای ذیل را دریافت خواهید کرد:
Error: getaddrinfo ENOTFOUND chromedriver.storage.googleapis.com chromedriver.storage.googleapis.com:443
@font-face { src: url('Font/BYekan.eot?#') format('eot'), /* IE6–8 */ url('Font/BYekan.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url('Font/BYekan.ttf') format('truetype'); /* Saf3—5, Chrome4+, FF3.5, Opera 10+ */ }
بعد از تست پروژه با فایرباگ متوجه شدم که متاسفانه اصلا فونت روی سیستم دانلود نشده و خروجی 404 که مربوط به File Not Found است برگشت داده میشود. سعی کردم با دادن آدرس فونت در برنامه download manager فونت را دانلود کنم. اما باز هم فونت دانلود نمیشد! (یعنی فایل روی سرور موجود بود ولی هیچ دسترسی به آن ممکن نبود) برای دو پسوند دیگر یعنی eot و ttf هم این کار را تست کردم و متوجه شدم روی این دو پسوند مشکل خاصی وجود ندارد. با تماس با پشتیبانی هاست متوجه شدم اصلا این پسوند در Mime Type های سرور تعریف نشده است . بنابراین به صورت دستی Mime Type مورد نظر را روی سرور تعریف کردم و مشکل حل شد:
همچنین لیست کامل Mime Typesها در آدرس زیر موجود است:
- LocalDb نیاز دارد که پروفایل کاربر بارگذاری شده باشد
- بصورت پیش فرض وهله LocalDb متعلق به یک کاربر بوده، و خصوصی است
در قسمت قبل دیدیم چگونه باید پروفایل کاربر را بدرستی بارگذاری کنیم. در این مقاله به مالکیت وهلهها (instance ownership) میپردازیم.
مشکل وهله خصوصی
در پایان قسمت قبلی، اپلیکیشن وب را در این حالت رها کردیم:
همانطور که مشاهده میکنید با خطای زیر مواجه هستیم:
System.Data.SqlClient.SqlException: Cannot open database "OldFashionedDB" requested by the login. The login failed.
Login failed for user 'IIS APPPOOL\ASP.NET v4.0'.
این بار پیغام خطا واضح و روشن است. LocalDb با موفقیت اجرا شده و اپلیکیشن وب هم توانسته به آن وصل شود، اما این کانکشن سپس قطع شده چرا که دسترسی به وهله جاری وجود نداشته است. اکانت ApplicationPoolIdentity (در اینجا IIS APPPOOL\ASP.NET v4.0) نتوانسته به دیتابیس LocalDb وارد شود، چرا که دیتابیس مورد نظر در رشته اتصال اپلیکیشن (OldFashionedDB) وجود ندارد. عجیب است، چرا که وصل شدن به همین دیتابیس با رشته اتصال جاری در ویژوال استودیو با موفقیت انجام میشود.
همانطور که در تصویر بالا مشاهده میکنید از ابزار SQL Server Object Explorer استفاده شده است. این ابزار توسط SQL Server Data Tools معرفی شد و در نسخههای بعدی ویژوال استودیو هم وجود دارد و توسعه یافته است. چطور ممکن است ویژوال استودیو براحتی بتواند به دیتابیس وصل شود، اما اپلیکیشن وب ما با همان رشته اتصال نمیتواند دیتابیس را باز کند؟ در هر دو صورت رشته اتصال ما بدین شکل است:
Data Source=(localdb)\v11.0;Initial Catalog=OldFashionedDB;Integrated Security=True
پاسخ این است که در اینجا، دو وهله از LocalDb وجود دارد. بر خلاف وهلههای SQL Server Express که بعنوان سرویسهای ویندوزی اجرا میشوند، وهلههای LocalDb بصورت پروسسهای کاربری (user processes) اجرا میشوند. هنگامی که کاربران مختلفی سعی میکنند به LocalDb متصل شوند، برای هر کدام از آنها پروسسهای مجزایی اجرا خواهد شد. هنگامی که در ویژوال استودیو به localdb)\v11.0) وصل میشویم، وهله ای از LocalDb ساخته شده و در حساب کاربری ویندوز جاری اجرا میشود. اما هنگامی که اپلیکیشن وب ما در IIS میخواهد به همین دیتابیس وصل شود، وهله دیگری ساخته شده و در ApplicationPoolIdentity اجرا میشود. گرچه ویژوال استودیو و اپلیکیشن ما هر دو از یک رشته اتصال استفاده میکنند، اما در عمل هر کدام به وهلههای متفاوتی از LocalDb دسترسی پیدا خواهند کرد. پس مسلما دیتابیسی که توسط وهله ای در ویژوال استودیو ساخته شده است، برای اپلیکیشن وب ما در IIS در دسترس نخواهد بود.
یک مقایسه خوب از این وضعیت، پوشه My Documents در ویندوز است. فرض کنید در ویژوال استودیو کدی بنویسیم که در این پوشه یک فایل جدید میسازد. حال اگر با حساب کاربری دیگری وارد ویندوز شویم و به پوشه My Documents برویم این فایل را نخواهیم یافت. چرا که پوشه My Documents برای هر کاربر متفاوت است. بهمین شکل، وهلههای LocalDb برای هر کاربر متفاوت است و به پروسسها و دیتابیسهای مختلفی اشاره میکنند.
به همین دلیل است که اپلیکیشن وب ما میتواند بدون هیچ مشکلی روی IIS Express اجرا شود و دیتابیس را باز کند. چرا که IIS Express درست مانند LocalDb یک پروسس کاربری است. IIS Express توسط ویژوال استودیو راه اندازی میشود و روی حساب کاربری جاری اجرا میگردد، پس پروسس آن با پروسس خود ویژوال استودیو یکسان خواهد بود و هر دو زیر یک اکانت کاربری اجرا خواهند شد.
راه حل ها
درک ماهیت مشکل جاری، راه حالهای مختلفی را برای رفع آن بدست میدهد. از آنجا که هر راه حل مزایا و معایب خود را دارد، بجای معرفی یک راه حال واحد چند راهکار را بررسی میکنیم.
رویکرد 1: اجرای IIS روی کاربر جاری ویندوز
اگر مشکل، حسابهای کاربری مختلف است، چرا خود IIS را روی کاربر جاری اجرا نکنیم؟ در این صورت ویژوال استودیو و اپلیکیشن ما هر دو به یک وهله از LocalDb وصل خواهند شد و همه چیز بدرستی کار خواهد کرد. ایجاد تغییرات لازم نسبتا ساده است. IIS را اجرا کنید و Application Pool مناسب را انتخاب کنید، یعنی همان گزینه که برای اپلیکیشن شما استفاده میشود.
قسمت Advanced Settings را باز کنید:
روی دکمه سه نقطه کنار خاصیت Identity کلیک کنید تا پنجره Application Pool Identity باز شود:
در این قسمت میتوانید از حساب کاربری جاری استفاده کنید. روی دکمه Set کلیک کنید و نام کاربری و رمز عبور خود را وارد نمایید. حال اگر اپلیکیشن را مجددا اجرا کنید، همه چیز باید بدرستی اجرا شود.
خوب، معایب این رویکرد چیست؟ مسلما اجرای اپلیکیشن وب روی اکانت کاربری جاری، ریسکهای امنیتی متعددی را معرفی میکند. اگر کسی بتواند اپلیکیشن وب ما را هک کند، به تمام منابع سیستم که اکانت کاربری جاری به آنها دسترسی دارد، دسترسی خواهد داشت. اما اجرای اپلیکیشن مورد نظر روی ApplicationPoolIdentity امنیت بیشتری را ارائه میکند، چرا که اکانتهای ApplicationPoolIdentity دسترسی بسیار محدودتری به منابع سیستم محلی دارند. بنابراین استفاده از این روش بطور کلی توصیه نمیشود، اما در سناریوهای خاصی با در نظر داشتن ریسکهای امنیتی میتواند رویکرد خوبی باشد.
رویکرد 2: استفاده از وهله مشترک
یک راه حال دیگر استفاده از قابلیت instance sharing است. این قابلیت به ما این امکان را میدهد تا یک وهله LocalDb را بین کاربران یک سیستم به اشتراک بگذاریم. وهله به اشتراک گذاشته شده، توسط یک نام عمومی (public name) قابل دسترسی خواهد بود.
سادهترین راه برای به اشتراک گذاشتن وهلههای LocalDb استفاده از ابزار SqlLocalDB.exe است. بدین منظور Command Prompt را بعنوان مدیر سیستم باز کنید و فرمان زیر را اجرا نمایید:
sqllocaldb share v11.0 IIS_DB
Data Source=(localdb)\.\IIS_DB;Initial Catalog=OldFashionedDB;Integrated Security=True
پیش از آنکه اپلیکیشن وب ما بتواند به این وهله متصل شود، باید لاگینهای مورد نیاز برای ApplicationPoolIdentity را ایجاد کنیم. راه اندازی وهله ساده است، کافی است دیتابیس را در SQL Server Object Explorer باز کنید. این کار اتصالی به دیتابیس برقرار میکند و آن را زنده نگاه میدارد. برای ایجاد لاگین مورد نظر، میتوانیم در SQL Server Object Explorer یک کوئری اجرا کنیم:
create login [IIS APPPOOL\ASP.NET v4.0] from windows; exec sp_addsrvrolemember N'IIS APPPOOL\ASP.NET v4.0', sysadmin
اسکریپت بالا به اکانت ApplicationPoolIdentity سطح دسترسی کامل میدهد. در صورت امکان بهتر است از سطوح دسترسی محدودتری استفاده کنید، مثلا دسترسی به دیتابیس یا جداولی مشخص. حالا میتوانید اپلیکیشن را مجددا اجرا کنید و همه چیز بدون خطا باید کار کند.
معایب این روش چیست؟ مشکل اصلی در این رویکرد این است که پیش از آنکه اپلیکیشن ما بتواند به وهله مشترک دسترسی داشته باشد، باید وهله مورد نظر را راه اندازی و اجرا کنیم. بدین منظور، حساب کاربری ویندوزی که مالکیت وهله را دارد باید به آن وصل شود و کانکشن را زنده نگه دارد، در غیر اینصورت وهله LocalDb قابل دسترسی نخواهد بود.
رویکرد 3: استفاده از SQL Server Express
از آنجا که نسخه کامل SQL Server Express بعنوان یک سرویس ویندوزی اجرا میشود، شاید بهترین راه استفاده از همین روش باشد. کافی است یک نسخه از SQL Server Express را نصب کنیم، دیتابیس مورد نظر را در آن بسازیم و سپس به آن متصل شویم. برای این کار حتی میتوانید از ابزار جدید SQL Server Data Tools استفاده کنید، چرا که با تمام نسخههای SQL Server سازگار است. در صورت استفاده از نسخههای کامل تر، رشته اتصال ما بدین شکل تغییر خواهد کرد:
Data Source=.\SQLEXPRESS;Initial Catalog=OldFashionedDB;Integrated Security=True
create login [IIS APPPOOL\ASP.NET v4.0] from windows; exec sp_addsrvrolemember N'IIS APPPOOL\ASP.NET v4.0', sysadmin
حال اجرای مجدد اپلیکیشن باید با موفقیت انجام شود. استفاده از این روش مسلما امکان استفاده از LocalDb را از ما میگیرد. ناگفته نماند که وهلههای SQL Server Express همیشه در حال اجرا خواهند بود چرا که بصورت سرویسهای ویندوزی اجرا میشوند. همچنین استفاده از این روش ممکن است شما را با مشکلاتی هم مواجه کند. مثلا خرابی رجیستری ویندوز میتواند SQL Server Express را از کار بیاندازد و مواردی از این دست. راهکارهای دیگری هم وجود دارند که در این مقاله به آنها نپرداختیم. مثلا میتوانید از AttachDbFilename استفاده کنید یا از اسکریپتهای T-SQL برای استفاده از وهله خصوصی ASP.NET کمک بگیرید. اما این روشها دردسرهای زیادی دارند، بهمین دلیل از آنها صرفنظر کردیم.
مطالعه بیشتر درباره LocalDb
آشنایی با Leaflet
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script> <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
#map { height: 600px; }
var map = L.map('map').setView([29.6760859,52.4950737], 13);
var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; var osmAttrib='Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'; var osm = new L.TileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib}).addTo(map);
Marker، دایره و چندضلعی
در کنار نمایش Tileها میتوان اشکال گرافیکی نیز به نقشه اضافه کرد؛ مثل مارکر(نقطه)، مستطیل، دایره و یا یک Popup. اضافه کردن یک Marker به سادگی، با کد زیر صورت میپذیرد:
var marker = L.marker([29.623116,52.497856]).addTo(map);
محل مورد نظر به شیء مارکر پاس داده شده و مقدار بازگشتی به map اضافه شده است.
نمایش چند ضلعی و دایره هم کار ساده ای است. برای دایره باید ابتدا مختصات مرکز دایره و شعاع به متر را به L.circle پاس داد:
var circle = L.circle([29.6308217,52.5048021], 500, { color: 'red', fillColor: '#f03', fillOpacity: 0.5 }).addTo(map);
در کد بالا علاوه بر محل و اندازه دایره، رنگ محیط، رنگ داخل و شفافیت (opacity) نیز مشخص شدهاند.
برای چند ضلعیها میتوان به این صورت عمل کرد:
var polygon = L.polygon([ [29.628453, 52.488838], [29.637368, 52.493987], [29.637168, 52.503987] ]).addTo(map);
کار کردن با Popup ها
از Popup میتوان برای نمایش اطلاعات اضافهای بر روی یک محل خاص یا یک عنوان به مانند Marker استفاده کرد. برای مثال میتوان اطلاعاتی دربارهی محل یک Marker یا دایره نمایش داد. در هنگام ایجاد marker، دایره و چندضلعی مقادیر بازگشتی در متغیرهای جدایی ذخیره شدند. اکنون میتوان به آن اشیاء یک popup اضافه کرد:
marker.bindPopup("باغ عفیف آباد").openPopup(); circle.bindPopup("I am a circle."); polygon.bindPopup("I am a polygon.");
به علت اینکه openPopup برای Marker صدا زده شده، به صورت پیشفرض popup را نمایش میدهد. اما برای بقیه، نمایش با کلیک خواهد بود. البته الزاما نیازی نیست که popup روی یک شیء نمایش داده شود، میتوان popupهای مستقلی نیز ایجاد کرد:
var popup = L.popup() .setLatLng([51.5, -0.09]) .setContent("I am a standalone popup.") .openOn(map);
تعاریف مداخل فایل index.html یک سایت AngularJS 2.0
پروژهای که در اینجا در حال استفاده است یک برنامهی ASP.NET MVC 5.x است؛ اما الزامی هم به استفادهی از آن وجود ندارد. یا یک فایل index.html را به ریشهی پروژه اضافه کنید و یا فایل Views\Shared\_Layout.cshtml را به نحو ذیل تغییر دهید:
<!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="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" /> <link href="~/app/app.component.css" rel="stylesheet"/> <link href="~/Content/Site.css" rel="stylesheet" type="text/css" /> <!-- 1. Load libraries --> <!-- IE required polyfills, in this exact order --> <script src="~/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script> <script src="~/node_modules/es6-shim/es6-shim.min.js"></script> <script src="~/node_modules/systemjs/dist/system-polyfills.js"></script> <script src="~/node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="~/node_modules/systemjs/dist/system.src.js"></script> <script src="~/node_modules/rxjs/bundles/Rx.js"></script> <script src="~/node_modules/angular2/bundles/angular2.dev.js"></script> <!-- Required for http --> <script src="~/node_modules/angular2/bundles/http.dev.js"></script> <!-- Required for routing --> <script src="~/node_modules/angular2/bundles/router.dev.js"></script> <!-- 2. Configure SystemJS --> <script> System.config({ packages: { app: { format: 'register', defaultExtension: 'js' } } }); System.import('app/main') .then(null, console.error.bind(console)); </script> </head> <body> <div> @RenderBody() <pm-app>Loading App...</pm-app> </div> @RenderSection("Scripts", required: false) </body> </html>
سپس کتابخانههای جاوا اسکریپتی مورد نیاز جهت کار با AngularJS 2.0 به ترتیبی که ذکر شده، باید تعریف شوند.
ذکر /~ در ابتدای آدرسها، مختص به ASP.NET MVC است. اگر از آن استفاده نمیکنید، نیازی به ذکر آن هم نیست.
در ادامه تعاریف System.JS ذکر شدهاست. System.JS کار بارگذاری ماژولهای برنامه را به عهده دارد. به این ترتیب دیگر نیازی نیست تا به ازای هر قسمت جدید برنامه، مدخلی را در اینجا اضافه کرد و کار بارگذاری آنها خودکار خواهد بود. فرمت register ایی که در اینجا ذکر شدهاست، تا ماژولهای استاندارد با فرمت ES 6 را نیز پشتیبانی میکند. همچنین با ذکر و تنظیم پسوند پیش فرض به js، دیگر نیازی نخواهد بود تا در حین تعریف importها در قسمتهای مختلف برنامه، پسوند فایلها را به صورت صریح ذکر کرد. مبحث improtها مرتبط است به مفاهیم ماژولها در ES 6 و همچنین TypeScript.
سطر System.import کار بارگذاری اولین ماژول برنامه را از پوشهی app قرار گرفته در ریشهی سایت انجام میدهد. این ماژول main نام دارد.
نوشتن اولین کامپوننت AngularJS 2.0
برنامههای AngularJS 2.0 متشکل هستند از تعدادی کامپوننت و سرویس:
و هر کامپوننت تشکیل شدهاست از:
- یک قالب یا Template: با استفاده از HTML تعریف میشود و کار تشکیل View و نحوهی رندر کامپوننت را مشخص میکند. در این Viewها با استفاده از امکانات binding و directives موجود در AngularJS 2.0 کار دسترسی به دادهها صورت میگیرد.
- یک کلاس: کار این کلاس که توسط TypeScript تهیه میشود، فراهم آوردن کدهای مرتبط با قالب است. برای مثال این کلاس حاوی تعدادی خاصیت خواهد بود که از اطلاعات آنها در View مرتبط استفاده میشود. همچنین این کلاس میتواند حاوی متدهای مورد نیاز در View نیز باشد؛ برای مثال متدی که کار نمایش یا مخفی سازی یک تصویر را با کلیک بر روی دکمهای انجام میدهد.
- متادیتا: متادیتا (یا decorator در اینجا) به AngularJS 2.0 اعلام میکند که این کلاس تعریف شده، صرفا یک کلاس ساده نیست و باید به آن به صورت یک کامپوننت نگاه شود.
در ذیل، کدهای یک کامپوننت نمونهی AngularJS 2.0 را مشاهده میکنید:
import { Component } from 'angular2/core'; @Component({ selector: 'pm-app', template:` <div><h1>{{pageTitle}}</h1> <div>My First Component</div> </div> ` }) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
بلافاصله در بالای این کلاس، متد decorator ایی را به نام Component مشاهده میکنید. این متادیتا است که به AngularJS 2.0 اعلام میکند، کلاس AppComponent تعریف شده، یک کامپوننت است و نه تنها یک کلاس ساده.
در متد Component تعریف شده، قالب یا template نحوهی رندر این کامپوننت را مشاهده میکنید.
در ابتدای این ماژول نیز کار import تعاریف مرتبط با متد ویژهی Component، از هستهی AngularJS 2.0 انجام شدهاست تا کامپایلر TypeScript بتواند این فایل ts را کامپایل کند.
مروری بر نحوهی تعریف class در TypeScript
مرور جامع کلاسها در TypeScript را در مطلب «مبانی TypeScript؛ کلاسها» میتوانید مطالعه کنید. در اینجا جهت یادآوری، خلاصهای از آنرا که نیاز داریم، بررسی خواهیم کرد:
- جهت تعریف یک کلاس، ابتدا واژهی کلیدی class به همراه نام کلاس ذکر میشوند.
- در AngularJS 2.0 مرسوم است که نام کلاس را به صورت نام ویژگی مدنظر به همراه پسوند Component ذکر کنیم؛ مانند AppComponent مثال فوق. این نام pascal case است و با حروف بزرگ شروع میشود.
- همچنین مرسوم است در برنامههای AngularJS 2.0، کامپوننت ریشهی سایت نیز AppComponent نامیده شود.
- در مثال فوق، واژهی کلیدی export را نیز پیش از واژهی کلیدی class مشاهده میکنید. به این ترتیب این کلاس خارج از ماژولی که در آن تعریف میشود، قابل دسترسی خواهد بود. اکنون این کلاس و فایل، تبدیل به ماژولی خواهند شد که توسط module loader معرفی شدهی در ابتدای بحث یا همان System.JS به صورت خودکار بارگذاری میشود و دیگر نیازی به تعریف مدخل script متناظر با آن در فایل index.html نخواهد بود.
- در بدنهی کلاس، کار تعریف متدها و خواص مورد نیاز View صورت میگیرند. برای نمونه در اینجا تنها یک خاصیت «عنوان صفحه» تعریف شدهاست. در جاوا اسکریپت مرسوم است که نام خواص را camel case شروع شده با حروف کوچک تعریف کنیم. سپس نوع این خاصیت به صورت رشتهای تعریف شدهاست و در آخر مقدار پیش فرض این خاصیت ذکر گردیدهاست.
البته باید دقت داشت که الزامی به ذکر نوع خاصیت، در TypeScript وجود ندارد. همینقدر که مقدار پیش فرض این خاصیت رشتهای است، بر اساس ویژگی به نام Type inference در TypeScript، نوع این خاصیت نیز رشتهای درنظر گرفته خواهد شد و دیگر نمیتوان برای مثال یک عدد را به آن انتساب داد.
- سطح دسترسی خواص تعریف شدهی در یک کلاس TypeScript به صورت پیش فرض public است. بنابراین در اینجا نیازی به ذکر صریح آن نبودهاست.
مروری بر متادیتا یا decorator یک کلاس در AngularJS 2.0
خوب، تا اینجا کلاس AppComponent تعریف و همچنین export شد تا توسط system.js به صورت خودکار بارگذاری شود. اما این کلاس به خودی خود صرفا یک کلاس TypeScript ایی است و توسط AngularJS شناسایی نمیشود. برای معرفی این کلاس به صورت یک کامپوننت، از یک تزئین کننده یا decorator ویژه به نام Component استفاده میشود که بلافاصله در بالای تعریف کلاس قرار میگیرد؛ چیزی شبیه به data annotations یا attributes در زبان #C.
یک decorator متدی است که اطلاعاتی اضافی را به یک کلاس، اعضاء و متدهای آن و یا حتی آرگومانهای آن متدها، الصاق میکند. این ویژگی قرار است به صورت استاندارد در ES 2016 یا نگارش بعدی جاوا اسکریپت حضور داشته باشد و در حال حاضر توسط TypeScript پشتیبانی شده و در نهایت به کدهای ES 5 قابل اجرای در تمام مرورگرها ترجمه میشود.
یک decorator همیشه با @ شروع میشود و AngularJS 2.0 به همراه تعدادی decorator توکار است؛ مانند Component. از آنجائیکه decorator یک متد است، همیشه به همراه یک جفت پرانتز () ذکر میشود و در انتهای آن نیازی به ذکر سمیکالن نیست. در اینجا تزئین کنندهی Component یک شیء را میپذیر که به همراه تعدادی خاصیت است. به همین جهت پارامتر آن به صورت {} ذکر شدهاست.
خاصیت selector یک کامپوننت مشخص میکند که نام directive متناظر با این کامپوننت چیست:
selector: 'pm-app',
<div> @RenderBody() <pm-app>Loading App...</pm-app> </div>
template:` <div><h1>{{pageTitle}}</h1> <div>My First Component</div> </div> `
همچنین {{}} به معنای تعریف data binding است. به این ترتیب مقداری که قرار است به صورت تگ h1 رندر شود، از خاصیت pageTitle کلاس مزین شدهی توسط این ویژگی یا decorator تامین خواهد شد؛ یعنی مقدار پیش فرض خاصیت pageTitle در کلاس AppComponent.
import اطلاعات مورد نیاز جهت کامپایل یک فایل TypeScript
تا اینجا مفاهیم موجود در کلاس AppComponent را به همراه متادیتای آن بررسی کردیم. اما این متادیتای جدید کامپوننت، به صورت پیش فرض ناشناختهاست:
همانطور که مشاهده میکنید، در اینجا ذیل کامپوننت، خط قرمزی جهت یادآوری عدم تعریف آن، کشیده شدهاست. در TypeScript و یا ES 6، پیش از استفاده از یک کلاس یا متد خارجی، نیاز است به module loader اعلام کنیم تا آنرا باید از کجا تامین کند. اینکار توسط عبارت import انجام میشود که شبیه به عبارت using در زبان سیشارپ است. عبارت import جزئی از استاندارد ES 6 است و همچنین در TypeScript نیز پشتیبانی میشود. به این ترتیب امکان دسترسی به ویژگیهای export شدهی از سایر ماژولها را در ماژول فعلی (فایل فعلی در حال کار) خواهیم یافت. نمونهی آنرا با ذکر واژهی کلیدی export پیش از کلاس AppComponent پیشتر ملاحظه کردید.
این ماژولهای خارجی میتوانند سایر ماژولها و فایلهای ts نوشته شدهی توسط خودمان و یا حتی اجزای AngularJS 2.0 باشند. طراحی AngularJS 2.0 نیز ماژولار است و از ماژولهایی مانند angular2/core، angular2/animation، angular2/http و angular2/router تشکیل شدهاست.
برای نمونه متادیتای کامپوننت، در ماژول angular2/core قرار دارد. به همین جهت نیاز است در ابتدای ماژول فعلی آنرا import کرد:
import { Component } from 'angular2/core';
ساخت کامپوننت ریشهی یک برنامهی AngularJS 2.0
در ابتدای بحث که تعاریف مداخل مورد نیاز جهت اجرای یک برنامهی AngularJS 2.0 ذکر شدند، عنوان شد که system.js به دنبال ماژول آغازین app/main میگردد.
بنابراین در ریشهی پروژه، پوشهی جدیدی را به نام app ایجاد کنید.
سپس یک فایل TypeScript جدید را به نام app.component.ts به این پوشه اضافه کنید. قالب این نوع فایلها در add new item و با جستجو typescript در دسترس است و یا حتی یک فایل متنی ساده را هم با پسوند ts ایجاد کنید، کافی است.
نامگذاری این فایلها هم مرسوم است به صورت ذکر نام ویژگی به همراه یک دات و سپس ذکر کامپوننت صورت گیرد. در اینجا چون قصد داریم کامپوننت ریشهی برنامه را ایجاد کنیم، نام ویژگی آن app خواهد بود و نام کامل فایل به این ترتیب app.component.ts میشود.
سپس محتوای این فایل را به دقیقا معادل کدهای قسمت «نوشتن اولین کامپوننت AngularJS 2.0» ابتدای بحث تغییر دهید (همان import، متادیتا و کلاس AppComponent).
تا اینجا کامپوننت ریشهی برنامه ایجاد شد. اما چگونه باید از آن استفاده کنیم و چگونه AngularJS 2.0 آنرا شناسایی میکند؟ به این فرآیند آغازین شناسایی و بارگذاری ماژولها، اصطلاحا bootstrapping میگویند. تنها صفحهی واقعی موجود در یک برنامهی تک صفحهای وب، همان فایل index.html است و سایر صفحات و محتوای آنها به محتوای این صفحهی آغازین اضافه یا کم خواهند شد.
<div> @RenderBody() <pm-app>Loading App...</pm-app> </div>
خوب، اکنون module loader یا system.js چگونه این pm-app یا کامپوننت ریشهی برنامه را شناسایی میکند؟
System.import('app/main')
/// <reference path="../node_modules/angular2/typings/browser.d.ts" /> import { bootstrap } from "angular2/platform/browser"; // Our main component import { AppComponent } from "./app.component"; bootstrap(AppComponent);
در انتهای این فایل متد bootstrap مربوط به AngularJS 2.0 را مشاهده میکنید. کار آن بارگذاری اولین ماژول و کامپوننت برنامه یا همان AppComponent است.
در ادامه نیاز است AppComponent را به این ماژول معرفی کرد؛ در غیراینصورت کامپایل نخواهد شد. برای این منظور سطر import این کلاس را از فایل app.component، مشاهده میکنید. در اینجا نیازی به ذکر پسوند ts. فایل app.component نیست.
سپس نیاز است محل تعریف متد بوت استرپ را نیز مشخص کنیم. این متد در ماژول angular2/platform/browser قرار دارد که به عنوان اولین import این فایل ذکر شدهاست.
سطر اول، مربوط است به تعریف فایلهای d.ts. مربوط به TypeScript جهت شناسایی نوعهای مرتبط با AngularJS 2.0. اگر اینکار صورت نگیرد، خطاهای ذیل را در حین کامپایل فایلهای TypeScript دریافت خواهید کرد:
node_modules\angular2\src\core\application_ref.d.ts(171,81): error TS2304: Build: Cannot find name 'Promise'. node_modules\angular2\src\core\change_detection\differs\default_keyvalue_differ.d.ts(23,15): error TS2304: Build: Cannot find name 'Map'.
تا اینجا پوشهی app واقع در ریشهی سایت، یک چنین شکلی را پیدا میکند:
و اکنون اگر برنامه را اجرا کنیم (فشردن دکمهی F5)، خروجی آن در مرورگر به صورت ذیل خواهد بود:
با توجه به اینکه در حال کار با یک برنامهی جاوا اسکریپتی هستیم، باز نگه داشتن developer tools مرورگر، جهت مشاهدهی خطاهای احتمالی ضروری است.
در اینجا اگر خطایی وجود داشته باشد، یا اطلاعات اضافی مدنظر باشد، در console لاگ خواهند شد. برای مثال در اینجا عنوان شدهاست که برنامه در حالت توسعه در حال اجرا است. بهتر است برای ارائهی نهایی، متد enableProdMode را در فایل index.html فراخوانی کنید.
همچنین در اینجا میتوان HTML نهایی تزریق شدهی به صفحه را بهتر مشاهده کرد:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part2.zip
برای اجرای آن، ابتدا به فایل project.json مراجعه کرده و یکبار آنرا ذخیره کنید تا وابستگیهای اسکریپتی پروژه از اینترنت دریافت شوند (این موارد در قسمت قبل مرور شدند). سپس یکبار هم پروژه را Build کنید تا تمام فایلهای ts آن کامپایل شوند و در آخر، اجرای نهایی پروژه.
خلاصهی بحث
یک برنامهی AngularJS 2.0 متشکل است از تعدادی کامپوننت. بنابراین کلاسی را ایجاد خواهیم کرد تا کدهای پشتیبانی کنندهی View این کامپوننت را تولید کند. سپس این کلاس را با متادیتایی مزین کرده و توسط آن تگ سفارشی ویژهی این کامپوننت و تگهای HTML تشکیل دهندهی این کامپوننت را به AngularJS 2.0 معرفی میکنیم. در اینجا در صورت نیاز وابستگیهای تعریف این متادیتا را توسط واژهی کلیدی import دریافت میکنیم. نام این کلاس بهتر است Pascal case بوده و با حروف بزرگ شروع شود و همچنین به صورت نام ویژگی ختم شدهی به کلمهی Component باشد. در اینجا حتما نیاز است این کلاس export شود تا توسط module loader قابل استفاده و بارگذاری گردد. اگر View این کامپوننت نیاز به دریافت اطلاعاتی دارد، این اطلاعات به صورت خواصی در کلاس کامپوننت تعریف میشوند. این خواص تعریف شدهی با سطح دسترسی عمومی، مرسوم است به صورت camel case تعریف شوند و حروف اول آنها کوچک باشد.
نگاهی به گزینههای مختلف مهیای جهت میزبانی SignalR
کلیه مثالهای این دوره برای استفاده از SignalR 2.x به روز شده و در مخزن کد ذیل قرار گرفتند:
SignalR-Samples
نحوهی ارتقاء برنامههای SignalR 1.x به SignalR 2.x
کلیه مثالهای این دوره برای استفاده از SignalR 2.x به روز شده و در مخزن کد ذیل قرار گرفتند:
SignalR-Samples