نظرات مطالب
EF Code First #10
سلام 
من در حین استفاده از EF-Profiler با خطای Exception has been thrown by the target of an invocation  مواجه میشم.
EF-profiler رو بعد از دانلود کردن اجرا میکنم، Dll مربوطه رو به برنامه اضافه کردم و برنامه رو Run میکنم . این خطا روی خط 
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
اتفاق می‌افتد.
روی کد پروژه خودتون هم تست کردم ، همین خطا رو میداد.
ممنون میشم راهنماییم کنید.
اشتراک‌ها
توصیه تیم کروم به عدم استفاده‌ی از alert/confirm/prompt

alert()/confirm()/prompt() dialogs are being changed. Rather than being app-modal, they will be dismissed when their tab is switched from. (Safari 9.1 already does this.) This is fully enabled on the canary and dev channels and partially enabled on the beta and stable channels, and will be enabled more in the future. 

توصیه تیم کروم به عدم استفاده‌ی از alert/confirm/prompt
مطالب
تشخیص خودکار کدهای تکراری در یک پروژه Visual Studio


آیا می‌دانید چند درصد از کدهای یک پروژه شما در قسمت‌های مختلف آن تکراری هستند و تا چه حد نیاز به refactoring کدهای موجود جهت مدیریت و نگهداری ساده‌تر از آن پروژه وجود دارد؟
اخیرا پروژه سورس بازی در سایت CodePlex به نام Clone detective ارائه شده است که این کار را به صورت خودکار با یکپارچه شدن با Visual studio برای شما انجام می‌دهد. این افزودنی از آدرس زیر قابل دریافت است:
http://www.codeplex.com/CloneDetectiveVS


بهترین آموزش نحوه استفاده از آن هم از طریق ویدیوی زیر در دسترس است:
مشاهده

در نگارش فعلی آن تنها پروژه‌های سی شارپ پشتیبانی می‌شوند و در نگارش‌های آتی آن قرار است VB.net و CPP نیز افزوده شوند.

به چه دلیلی به این ابزار نیاز داریم؟
فرض کنید کلاسی را جهت انجام مقصودی خاص توسعه داده‌اید. در کلاسی دیگر برای اتمام آن، 15 سطر از یکی از توابع کلاس اول را کپی کرده و مورد استفاده قرار داده‌اید. در یک پروژه بزرگ از این موارد شاید زیاد رخ دهد (خصوصا در یک کار تیمی که ممکن است قسمتی از کار شما به‌عنوان پایه اولیه کاری دیگر مورد استفاده قرار گیرد). پس از مدتی، تغییراتی را در کلاس اول ایجاد کرده و یک سری از عیوب آن 15 سطر را که جزئی از یک تابع است برطرف خواهید کرد. بسیار هم خوب! آیا این پایان کار است؟ خیر!
آیا این مورد به کل پروژه منتقل شده است؟ آیا نگهداری یک پروژه بزرگ که دارای قسمت‌های تکراری زیادی است کار ساده‌ای است؟

علاوه بر ابزار فوق، برنامه دیگری نیز جهت تشخیص کدهای تکراری در یک پروژه به نام Simian موجود است. Simian را از آدرس زیر می‌توانید دریافت کنید:
http://www.redhillconsulting.com.au/products/simian/overview.html
این ابزار به صورت یک افزودنی VS.net ارائه نشده است اما می‌توان از طریق منوی tools آنرا به مجموعه ابزارهای مورد استفاده اضافه کرد. نحوه انجام اینکار به صورت مصور در وبلاگ زیر بیان شده است:
مشاهده

همچنین از ابزارهای دیگری از این دست می‌توان به برنامه رایگان CCFinder‌ اشاره کرد: (ثبت نام دریافت آن رایگان است)
http://www.ccfinder.net


مطالب دوره‌ها
استفاده از XQuery - قسمت دوم
در ادامه‌ی مباحث XQuery، سایر قابلیت‌های توکار SQL Server را برای کار با اسناد XML بررسی خواهیم کرد.

کوئری گرفتن از اسناد XML دارای فضای نام، توسط XQuery

در مثال زیر، تمام المان‌های سند XML، در فضای نام http://www.people.com تعریف شده‌اند.
DECLARE @doc XML 
SET @doc ='
<p:people xmlns:p="http://www.people.com">
 <p:person name="Vahid" /> 
 <p:person name="Farid" />
</p:people>
'
SELECT @doc.query('/people/person')
اگر کوئری فوق را برای یافتن اشخاص اجرا کنیم، خروجی آن خالی خواهد بود (و یا یک empty sequence)؛ زیرا کوئری نوشته شده به دنبال اشخاصی است که در فضای نام خاصی تعریف نشده‌اند.
سعی دوم احتمالا روش ذیل خواهد بود
 SELECT @doc.query('/p:people/p:person')
که به خطای زیر منتهی می‌شود:
 XQuery [query()]: The name "p" does not denote a namespace.
برای حل این مشکل باید از مفهومی به نام prolog استفاده کرد. هر XQuery از دو قسمت prolog و body تشکیل می‌شود. قسمت prolog می‌تواند شامل تعاریف فضاهای نام، متغیرها، متدها و غیره باشد و قسمت body، همان کوئری تهیه شده‌است. البته SQL Server از قسمت prolog استاندارد XQuery، فقط تعریف فضاهای نام آن‌را مطابق مثال ذیل پشتیبانی می‌کند:
 SELECT @doc.query('
declare default element namespace "http://www.people.com";
/people/person
')
یک سند XML ممکن است با بیش از یک فضای نام تعریف شود. در این حالت خواهیم داشت:
 SELECT @doc.query('
declare namespace aa="http://www.people.com";
/aa:people/aa:person
')
در اینجا در قسمت prolog، برای فضای نام تعریف شده در سند XML، یک پیشوند را تعریف کرده و سپس، استفاده از آن مجاز خواهد بود.
روش دیگر تعریف فضای نام، استفاده از WITH XMLNAMESPACES، پیش از تعریف کوئری است:
 WITH XMLNAMESPACES(DEFAULT 'http://www.people.com')
SELECT @doc.query('/people/person')
البته باید دقت داشت، زمانیکه WITH XMLNAMESPACES تعریف می‌شود، عبارت T-SQL پیش از آن باید با یک سمی‌کالن خاتمه یابد؛ و گرنه یک خطای دستوری خواهید گرفت.
در اینجا نیز امکان کار با چندین فضای نام وجود دارد و برای این منظور تنها کافی است از تعریف Alias استفاده شود. فضاهای نام بعدی با یک کاما از هم مجزا خواهند شد.
 WITH XMLNAMESPACES('http://www.people.com' AS aa)
SELECT @doc.query('/aa:people/aa:person')


عبارات XPath و FLOWR

XQuery از دو نوع عبارت XPath و FLOWR می‌تواند استفاده کند. XQuery همیشه از XPath برای انتخاب داده‌ها و نودها استفاده می‌کند. در اینجا هر نوع XPath سازگار با استاندارد 2 آن، یک XQuery نیز خواهد بود. برای انجام اعمالی بجز انتخاب داده‌ها، باید از عبارات FLOWR استفاده کرد؛ برای مثال برای ایجاد حلقه، مرتب سازی و یا ایجاد نودهای جدید.
در مثال زیر که data آن در قسمت قبل تعریف شد، دو کوئری نوشته شده یکی هستند:
 SELECT @data.query('
 (: FLOWE :)
 for $p in /people/person
 where $p/age > 30
 return $p
 ')

SELECT @data.query('
(: XPath :)
/people/person[age>30]
')
اولین کوئری به روش FLOWR تهیه شده‌است و دومین کوئری از استاندارد XPath استفاده می‌کند. از دیدگاه SQL Server این دو یکی بوده و حتی Query Plan یکسانی نیز دارند.

 XPath بسیار شبیه به مسیر دهی‌های یونیکسی است. بسیار فشرده بوده و همچنین مناسب است برای کار با ساختارهای تو در تو و سلسله مراتبی. مثال زیر را درنظر بگیرید:
 /books/book[1]/title/chapter
در اینجا books، المان ریشه است. سپس به اولین کتاب این ریشه اشاره می‌شود. سپس به المان عنوان و مسیر نهایی، به فصل ختم می‌شود. البته همانطور که در قسمت‌های پیشین نیز ذکر شد، حالت content، پیش فرض بوده و یک فیلد XML می‌تواند دارای چندین ریشه باشد.

در XPath توسط قابلیتی به نام محور می‌توان به المان‌های قبلی یا بعدی دسترسی پیدا کرد. این محورهای پشتیبانی شده در SQL Server عبارتند از self (خود نود)، child (فرزند نود)، parent (والد نود)، decedent (فرزند فرزند فرزند ...)و attribute (دسترسی به ویژگی‌ها). محورهای استانداردی مانند preceding-sibling و following-sibling در SQL Server با عملگرهایی مانند >> و << پشتیبانی می‌شوند.



مثال‌هایی از نحوه‌ی استفاده از محورهای XPath

اینبار قصد داریم یک سند XML نسبتا پیچیده را بررسی کرده و اجزای مختلف آن‌را به کمک XPath بدست بیاوریم.
DECLARE @doc XML 
SET @doc='
<Team name="Project 1" xmlns:a="urn:annotations">
  <Employee id="544" years="6.5">
    <Name>User 1</Name>
<Title>Architect</Title>
<Expertise>Games</Expertise>
<Expertise>Puzzles</Expertise>
<Employee id="101" years="7.1" a:assigned-to="C1">
 <Name>User 2</Name>
 <Title>Dev lead</Title>
 <Expertise>Video Games</Expertise>
 <Employee id="50" years="2.3" a:assigned-to="C2">
 <Name>User 3</Name>
 <Title>Developer</Title>
 <Expertise>Hardware</Expertise>
 <Expertise>Entertainment</Expertise>
</Employee>
</Employee> 
  </Employee>
</Team>
'
در این سند، کارمند و کارمندانی را که باید به یک کارمند گزارش دهند، ملاحظه می‌کنید.
در XPath، محور پیش فرض، child است (اگر مانند کوئری زیر مورد خاصی ذکر نشود):
 SELECT @doc.query('/Team/Employee/Name')
و اگر بخواهیم این محور را به صورت صریح ذکر کنیم، به نحو ذیل خواهد بود:
 SELECT @doc.query('/Team/Employee/child::Name')
خروجی آن User1 است.
 <Name>User 1</Name>
برای ذکر محور decedent-or-self می‌توان از // نیز استفاده کرد:
 SELECT @doc.query('//Employee/Name')
با خروجی
 <Name>User 1</Name>
<Name>User 2</Name>
<Name>User 3</Name>
در این حالت به تمام نودهای سند، در سطوح مختلف آن مراجعه شده و به دنبال نام کارمند خواهیم گشت.

برای کار با ویژگی‌ها و attributes از [] به همراه علامت @ استفاده می‌شود:
 SELECT @doc.query('
declare namespace a = "urn:annotations";
//Employee[@a:assigned-to]/Name
')
در این کوئری، تمام کارمندانی که دارای ویژگی assigned-to واقع در فضای نام urn:annotations هستند، یافت خواهند شد. با خروجی:
 <Name>User 2</Name>
<Name>User 3</Name>
معادل طولانی‌تر آن ذکر کامل محور attribute است بجای @
 SELECT @doc.query('
declare namespace a = "urn:annotations";
//Employee[attribute::a:assigned-to]/Name
')
و برای یافتن کارمندانی که دارای ویژگی assigned-to نیستند، می‌توان از عملگر not استفاده کرد:
 SELECT @doc.query('
declare namespace a = "urn:annotations";
//Employee[not(@a:assigned-to)]/Name
')
با خروجی
 <Name>User 1</Name>
و اگر بخواهیم تعداد کارمندانی را که به user 1 مستقیما گزارش می‌دهند را بیابیم، می‌توان از count به نحو ذیل استفاده کرد:
 SELECT @doc.query('count(//Employee[Name="User 1"]/Employee)')

در XPath برای یافتن والد از .. استفاده می‌شود:
 SELECT @doc.query('//Employee[../Name="User 1"]')
برای مثال در کوئری فوق، کارمندانی که والد آن‌ها user 1 هستند، یافت می‌شوند.
استفاده از .. در SQL Server به دلایل کارآیی پایین توصیه نمی‌شود. بهتر است از همان روش قبلی کوئری تعداد کارمندانی که به user 1 مستقیما گزارش می‌دهند، استفاده شود.



عبارات FLOWR

FLOWR هسته‌ی XQuery را تشکیل داده و قابلیت توسعه XPath را دارد. FLOWR مخفف for، let، order by، where و retrun است. از for برای تشکیل حلقه، از let برای انتساب، از where و order by برای فیلتر و مرتب سازی اطلاعات و از return برای بازگشت نتایج کمک گرفته می‌شود. FLOWR بسیار شبیه به ساختار SQL عمل می‌کند.
معادل عبارت SQL
 Select p.name, p.job
from people as p
where p.age > 30
order by p.age
با عبارات FLOWR، به صورت زیر است:
 for $p in /people/person
where $p.age > 30
order by $p.age[1]
return ($p/name, $p/job)
همانطور که مشاهده می‌کنید علت انتخاب FLOWR در اینجا عمدی بوده‌است؛ زیرا افرادی که SQL می‌دانند به سادگی می‌توانند شروع به کار با عبارات FLOWR کنند.
تنها تفاوت مهم، در اینجا است که در عبارات SQL، خروجی کار توسط select، در ابتدای کوئری ذکر می‌شود، اما در عبارات FLOWR در انتهای آن‌ها.

از let برای انتساب مجموعه‌ای از نودها استفاده می‌شود:
 let $p := /people/person
return $p
تفاوت آن با for در این است که در هر بار اجرای حلقه‌ی for، تنها با یک نود کار خواهد شد، اما در let با مجموعه‌ای از نودها سر و کار داریم. همچنین let از نگارش 2008 اس کیوال سرور به بعد قابل استفاده‌است.

یک نکته
اگر به order by  دقت کنید، به اولین سن اشاره می‌کند. Order by در اینجا با تک مقدارها کار می‌کند و امکان کار با مجموعه‌ای از نودها را ندارد. به همین جهت باید طوری آن‌را تنظیم کرد که هربار فقط به یک مقدار اشاره کند.
هر زمانیکه به خطای requires a singleton برخوردید، یعنی دستورات مورد استفاده با یک سری از نودها کار نکرده و نیاز است دقیقا مشخص کنید، کدام مقدار مدنظر است.


مثال‌هایی از عبارات FLOWR

دو کوئری ذیل یک خروجی 1 2 3 را تولید می‌کنند
 DECLARE @x XML = '';
SELECT @x.query('
for $i in (1,2,3)
return $i
');

SELECT @x.query('
let $i := (1,2,3)
return $i
');
در کوئری اول، هر بار که حلقه اجرا می‌شود، به یکی از اعضای توالی دسترسی خواهیم داشت. در کوئری دوم، یکبار توالی تعریف شده و کار با آن در یک مرحله صورت می‌گیرد.
در ادامه اگر سعی کنیم به این کوئری‌ها یک order by را اضافه کنیم، کوئری اول با موفقیت اجرا شده،
 DECLARE @x XML = '';
SELECT @x.query('
for $i in (1,2,3)
order by $i descending
return $i
');

SELECT @x.query('
let $i := (1,2,3)
order by $i descending
return $i
');
اما کوئری دوم با خطای ذیل متوقف می‌شود:
 XQuery [query()]: 'order by' requires a singleton (or empty sequence), found operand of type 'xs:integer +'
در خطا عنوان شده‌است که مطابق تعریف، order by با یک مجموعه از نودها، مانند حاصل let کار نمی‌کند و همانند حلقه for نیاز به singleton یا atomic values دارد.


ساخت المان‌های جدید XML توسط عبارات FLOWR

ابتدا همان سند XML قسمت قبل را درنظر بگیرید:
DECLARE @doc XML  =' 
<people>
 <person>
  <name>
<givenName>name1</givenName>
<familyName>lname1</familyName>
  </name>
  <age>33</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name2</givenName>
<familyName>lname2</familyName>
  </name>
  <age>40</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name3</givenName>
<familyName>lname3</familyName>
  </name>
  <age>30</age>
  <height>medium</height>
 </person>
</people>
'
در ادامه قصد داریم، المان‌های اشخاص را صرفا بر اساس مقدار givenName آن‌ها بازگشت دهیم:
 SELECT @doc.query('
for $p in /people/person
return <person>
{$p/name[1]/givenName[1]/text()}
</person>
');
در اینجا نحوه‌ی تولید پویای تگ‌های XML را توسط FLOWR مشاهده می‌کنید. عبارات داخل {} به صورت خودکار محاسبه و جایگزین می‌شوند و خروجی آن به شرح زیر است:
 <person>name1</person>
<person>name2</person>
<person>name3</person>

سؤال: اگر به این خروجی بخواهیم یک root element اضافه کنیم، چه باید کرد؟ اگر المان root دلخواهی را در return قرار دهیم، به ازای هر آیتم یافت شده، یکبار تکرار می‌شود که مدنظر ما نیست.
 SELECT @doc.query('
<root>
{
for $p in /people/person
return <person>
{$p/name[1]/givenName[1]/text()}
</person>
}
</root>
');
بله. در این حالت نیز می‌توان از همان روشی که در return استفاده کردیم، برای کل حلقه و return آن استفاده کنیم. المان root به صورت استاتیک محاسبه می‌شود و هر آنچه که داخل {} باشد، به صورت پویا. با این خروجی:
 <root>
  <person>name1</person>
  <person>name2</person>
  <person>name3</person>
</root>


مفهوم quantification در FLOWR

همان سند Team name=Project 1 ابتدای بحث جاری را درنظر بگیرید.
 SELECT @doc.query('some $emp in //Employee satisfies $emp/@years >5')
-- true

SELECT @doc.query('every $emp in //Employee satisfies $emp/@years >5')
-- false
به عبارات some و every در اینجا quantification گفته می‌شود. در کوئری اول، می‌خواهیم بررسی کنیم، آیا در بین کارمندان، بعضی از آن‌ها دارای ویژگی (با @ شروع شده) years بیشتر از 5 هستند. در کوئری دوم، عبارت «بعضی» به «هر» تغییر یافته است. 
مطالب
استفاده از کتابخانه‌های ثالث جاوا اسکریپتی در برنامه‌های AngularJS 2.0
هزاران کتابخانه‌ی جاوا اسکریپتی مستقل از AngularJS 2.0 وجود دارند که نیاز است بتوانیم از آن‌ها در برنامه‌های مختلفی استفاده کنیم. در این مطلب، دو روش بارگذاری آن‌ها را بررسی خواهیم کرد.


هدف: استفاده از کتابخانه‌ی jsSHA

می‌خواهیم در یک برنامه‌ی AngularJS 2.0، از کتابخانه‌ی jsSHA استفاده کرده و هش SHA512 یک رشته را محاسبه کنیم.


تامین پیشنیازهای اولیه

می‌توان فایل‌های این کتابخانه را مستقیما از GitHub دریافت و به پروژه اضافه کرد. اما بهتر است این‌کار را توسط npm مدیریت کنیم. به همین جهت فایل package.json آن‌را گشوده و سپس مدخل متناظری را به آن اضافه کنید:
"dependencies": {
    // ...
    "jssha": "^2.1.0",
    // ...
  },
به این ترتیب با اجرای دستور npm install بر روی پوشه‌ی جاری و یا ذخیره‌ی فایل در ویژوال استودیو، کار دریافت خودکار این کتابخانه صورت گرفته و در مسیر node_modules\jssha\src ذخیره می‌شود.


بارگذاری فایل‌های کتابخانه به صورت پویا

یک روش استفاده از این کتابخانه یا هر کتابخانه‌ی جاوا اسکریپتی، افزودن مدخل تعریف آن به صفحه‌ی index.html است:
 <script src="node_modules/jssha/src/sha512.js"></script>
این روش هر چند کار می‌کند، اما با توجه به اینکه AngularJS 2.0 از System.JS برای مدیریت ماژول‌های خود کمک می‌گیرد، می‌تواند با روش پویای ذیل جایگزین شود. برای این منظور ابتدا فایل systemjs.config.js را باز کنید و سپس دو تغییر ذیل را به آن اعمال نمائید:
// map tells the System loader where to look for things
var map = {
    // ...
    'jssha': 'node_modules/jssha/src'
};
 
// packages tells the System loader how to load when no filename and/or no extension
var packages = {
    // ...
    'jssha': { main: 'sha512.js', defaultExtension: 'js' }
};
در اینجا به اشیاء map و packages آن فایل که کار بارگذاری ماژول‌ها را به صورت خودکار انجام می‌دهد، تعاریف جدید jssha را اضافه کرده‌ایم. در قسمت map، مسیر پوشه‌ی فایل‌های js این کتابخانه مشخص شده‌اند و در قسمت packages، نام فایل اصلی مدنظر و پسوندهای آن‌ها ذکر گردید‌ه‌اند.
به این ترتیب هر زمانیکه کار import این کتابخانه صورت گیرد، بارگذاری پویای آن انجام خواهد شد. به علاوه ابزارهای بسته بندی و deploy پروژه هم این فایل را پردازش کرده و به صورت خودکار، کار bundling، فشرده سازی و یکی سازی اسکریپت‌ها را انجام می‌دهند.


استفاده از jsSHA به صورت untyped

پس از دریافت بسته‌های این کتابخانه و مشخص سازی نحوه‌ی بارگذاری پویای آن، اکنون نوبت به استفاده‌ی از آن است. در اینجا منظور از untyped این است که فرض کنیم برای این کتابخانه، فایل‌های typings مخصوص TypeScript وجود ندارند و پس از جستجوی در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped نتوانسته‌ایم معادلی را برای آن پیدا کنیم. بنابراین فایل جدید untyped-sha.component.ts را با محتوای ذیل به پروژه اضافه کنید:
import { Component, OnInit } from '@angular/core';
 
var jsSHA = require("jssha"); // ==> loads `sha512.js` file dynamically using `systemjs.config.js` file definitions
//declare var jsSHA: any; // ==> this requires adding <script src="node_modules/jssha/src/sha512.js"></script> to the first page manually.
 
@Component({
    templateUrl: 'app/using-third-party-libraries/untyped-sha.component.html'
})
export class UnTypedShaComponent implements OnInit {
    hash: String;
 
    ngOnInit(): void {
        let shaObj = new jsSHA("SHA-512", "TEXT");
        shaObj.update("This is a test");
        this.hash = shaObj.getHash("HEX");
    }
}
با این قالب untyped-sha.component.html
<h1>SHA-512 Hash / UnTyped</h1>
 
<p>String: This is a test</p>
<p>HEX: {{hash}}</p>
توضیحات
هر زمانیکه فایل‌های typing یک کتابخانه‌ی جاوا اسکریپتی در دسترس نبودند، فقط کافی است از روش declare var jsSHA: any استفاده کنید. در اینجا any به همان حالت استاندارد و بی‌نوع جاوا اسکریپت اشاره می‌کند. در این حالت برنامه بدون مشکل کامپایل خواهد شد؛ اما از تمام مزایای TypeScript مانند بررسی نوع‌ها و همچنین intellisense محروم می‌شویم.
در این مثال در hook ویژه‌ای به نام OnInit، کار ساخت شیء SHA را انجام داده و سپس هش عبارت This is a test محاسبه شده و به خاصیت عمومی hash انتساب داده می‌شود. سپس این خاصیت عمومی، در قالب این کامپوننت از طریق روش interpolation نمایش داده شده‌است.

دو نکته‌ی مهم
الف) اگر از روش declare var jsSHA: any استفاده کردید، کار بارگذاری فایل sha512.js به صورت خودکار رخ نخواهد داد؛ چون ماژولی را import نمی‌کند. بنابراین تعاریف systemjs.config.js ندید گرفته خواهد شد. در این حالت باید از همان روش متداول افزودن تگ script این کتابخانه به فایل index.html استفاده کرد.
ب) برای بارگذاری پویای کتابخانه‌ی jsSHA بر اساس تعاریف فایل systemjs.config.js از متد require کمک بگیرید:
 var jsSHA = require("jssha");
در این حالت باز هم متغیر jsSHA تعریف شده از نوع any است؛ اما اینبار متد require کار بارگذاری خودکار ماژولی را به نام jssha، انجام می‌دهد. این بارگذاری هم بر اساس تعاریف قسمت «بارگذاری فایل‌های کتابخانه به صورت پویا» ابتدای بحث کار می‌کند.


استفاده از jsSHA به صورت typed

کتابخانه‌ی jsSHA در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/jssha دارای فایل d.ts. مخصوص خود است. برای نصب آن از یکی از دو روش ذیل استفاده کنید:
الف) نصب دستی فایل‌های typings
 npm install -g typings
typings install jssha --save --ambient
توسط خط فرمان، به پوشه‌ی ریشه‌ی پروژه وارد شده و دو دستور فوق را صادر کنید. به این ترتیب فایل‌های d.ts. لازم، به پوشه‌ی typings پروژه اضافه می‌شوند.
ب) تکمیل فایل typings.ts
{
    "ambientDependencies": {
         // ...
        "jssha": "registry:dt/jssha#2.1.0+20160317120654"
    }
}
برای این منظور فایل typings.json را گشوده و سپس سطر جدید فوق را به آن اضافه کنید. اکنون اگر فایل package.json را یکبار دیگر ذخیره کنید و یا دستور npm install را صادر کنید، همان مراحل قسمت الف تکرار خواهند شد.

پس از نصب فایل‌های typings این پروژه، به فایل main.ts مراجعه کرده و مدخل ذیل را به ابتدای آن اضافه کنید:
/// <reference path="../typings/browser/ambient/jssha/index.d.ts" />
اینکار سبب خواهد شد تا intellisense درون ویژوال استودیو بتواند مداخل متناظر را یافته و راهنمای مناسبی را ارائه دهد.

در ادامه فایل جدید typed-sha.component.ts را با محتوای ذیل به پروژه اضافه کنید:
import { Component, OnInit } from '@angular/core';
//import { jsSHA } from "jssha";
import * as jsSHA from "jssha"; // ===> var jsSHA = require("jssha"); // ===> loads `sha512.js` file dynamically using `systemjs.config.js` file definitions
 
@Component({
    templateUrl: 'app/using-third-party-libraries/typed-sha.component.html'
})
export class TypedShaComponent implements OnInit{
    hash: String;
 
    ngOnInit(): void {
        let shaObj = new jsSHA("SHA-512", "TEXT");
        shaObj.update("This is a test");
        this.hash = shaObj.getHash("HEX");
    }
}
محتویات فایل typed-sha.component.html با محتویات فایل untyped-sha.component.html که پیشتر عنوان شد، یکی است.
در اینجا تنها نکته‌ی مهم و جدید نسبت به روش قبل (استفاده از jsSHA به صورت untyped)، روش import این کتابخانه است. روش "import * as jsSHA from "jssha به عبارت var jsSHA = require("jssha") ترجمه می‌شود که در نهایت سبب بارگذاری خودکار فایل‌های jssha بر اساس تعاریف مداخل آن در فایل systemjs.config.js می‌گردد.


کدهای کامل این پروژه را از اینجا می‌توانید دریافت کنید.
مطالب
بررسی اینترفیس ICommand در WPF
مدتی هست که مشغول مطالعه و یادگیری WPF از طریق مطالب سایت هستم؛ به همین خاطر تصمیم گرفتم مطلبی را حول محور اینترفیس ICommnad  گردآوری کنم و در اختیار کاربران سایت قرار دهم.

سرفصل‌های این مطلب :
• Command چیست
• اینترفیس ICommand چیست 
• چرا اینترفیس ICommand
• ایجاد UI مورد نیاز 
• چگونگی استفاده از ICommand 
• استفاده از INotifyPropertyChanged

Command چیست ؟
در برنامه نویسی WPF به هر کلاسی که اینترفیس ICommand را پیاده سازی کند، اصطلاحا Commnad گوییم. تفاوت کوچکی بین یک Event و Command وجود دارد. رخداد‌ها برای کنترل‌های UI ساخته و تخصیص داده می‌شوند؛ اما Command‌ها انتزاعی‌تر هستند و تمرکز آنها بر روی نحوه‌ی انجام کارها می‌باشد.
برای تعاملات در برنامه‌ها از Commandها و Event‌ها استفاده می‌کنیم. ما در WPF دو روش برای تعامل با UI داریم:
1- استفاده از Event‌ها و نوشتن کد‌های مورد نیاز در بخش CodeBehind (رعایت نکردن الگوی MVVM).
WPF تعداد زیادی RoutedEvent پیش ساخته (Built In) دارد که از آنها می‌توان در این روش استفاده کرد. 
2- استفاده از Command و درگیر کردن کدهای اجرایی نوشته شده در ViewModel با استفاده از Command ها.
در زمان استفاده از الگوی MVVM مجاز به نوشتن کد در بخش CodeBehind نیستیم. بنابراین از سیستم رخداد‌های طراحی شده‌ی در WPF که RoutedEvent می‌باشد، نمی‌توان استفاده کرد. به این خاطر که رخداد‌ها در ViewModel قابل دسترسی نمی‌باشد.

اینترفیس ICommand:
این اینترفیس سه عضو دارد که آن‌ها را در جدول زیر مشاهده می‌کنید:

نام عضو

توضیحات

Bool CanExecute(object parameter)

این تابع پارامتری از نوع object را دریافت می‌کند و یک مقدار bool را به خروجی تابع می‌فرستد. اگر مقدار خروجی متد، true  باشدcommand  اجرا خواهد شد و در غیر اینصورت اتفاقی رخ نخواهد داد. اغلب ازDelegate  ها به عنوان پارامتر این تابع استفاده می‌شود؛Delegate های پیش ساخته‌ای همچون Action,Predicate,Func

Event EventHandler CanExecuteChanged

این رویداد برای آگاه سازی UI که به Command متصل است، استفاده می‌شود .بر اساس خروجی تابع CanExecute، این رویداد اتفاق می‌افتد.

Void Execute(Object parameter)

این متد کار اصلی را در Command‌ها انجام می‌دهد. این متد تنها زمانی اجرا می‌شود که متدCanExecute  مقدار true را بازگرداند. این تابع پارامتری را از نوع object دریافت می‌کند، اما عموما ما یکDelegate  را به آن ارسال می‌کنیم. Delegate ارجاعی را به متدی، در خود نگاه می‌دارد که انتظار داریم در صورت اجرایcommand ، اجرا شود.


چرا اینترفیس ICommand :
هسته‌ی اصلی Command‌ها در WPF، اینترفیس ICommand می‌باشد. این اینترفیس به‌صورت گسترده‌ای در الگوی MVVM و Prism  استفاده شده است و استفاده‌ی از آن محدود به MVVM نمی‌باشد. تعداد زیادی Commnad بصورت پیش ساخته وجود دارند که این اینترفیس را پیاده سازی کرده‌اند .کلاس پایه RoutedCommand اینترفیس ICommand را پیاده سازی کرده است و WPF فرمان‌های زیر را فراهم کرده است:
• MediaCommnads
• ApplicationCommnads
• NavigationCommands
• ComponentCommnads
• EditingCommnads
که همگی از کلاس RoutedCommand استفاده کرده‌اند.
در این مطلب به دنبال ایجاد برنامه‌ای هستیم که حاصل جمع مفدار دو Textbox را پس از فشردن کلید جمع در یک textblock نمایش دهد.

ساخت UI مورد نیاز :
گام اول : 
با اجرای ویژوال استودیو، برنامه‌ای را با نام ICommnadSample ایجاد کنید. ساختار پروژه به شکل زیر است:
همانطور که می‌بینید View و ViewModel و در نهایت Command‌ها در پوشه‌های مجزایی ساماندهی شده‌اند.

گام دوم:
ابتدا قالب گرافیکی را مشخص می‌کنیم. در پوشه‌ی Views یک UserControl را به نام CalculatorView.xaml ایجاد کرده و کد زیر را در آن نوشتیم:
<Grid DataContext="{Binding Source={StaticResource calculatorVM}}" Background="#FFCCCC">
        <Grid.RowDefinitions>
            <RowDefinition Height="80"/>
            <RowDefinition/>
            <RowDefinition Height="80"/>
            <RowDefinition Height="44"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" FontSize="25"
               VerticalAlignment="Top" HorizontalAlignment="Center" Foreground="Blue" Content="ICommand Sample"/>
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
               Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="First Input"/>
        <Label Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" 
               Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="Second Input"/>

        <TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20" 
                 HorizontalAlignment="Left" Height="30"  Width="150" TextAlignment="Center" Text="{Binding FirstValue, Mode=TwoWay}"/>
        <TextBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20"
                 HorizontalAlignment="Left"  Height="30" Width="150" TextAlignment="Center" Text="{Binding SecondValue, Mode=TwoWay}"/>

        <Rectangle Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Fill="LightBlue"/>
        <Button Grid.Row="2" Grid.Column="0" Content="+"  Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding AddCommand}"/>
        
        <Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" FontSize="25" Margin="10,0,0,0" HorizontalAlignment="Left" Height="50"  Content="Result : "/>
        <TextBlock Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" FontSize="20" Margin="10,0,0,0" Background="BlanchedAlmond"
                   TextAlignment="Center"  HorizontalAlignment="Left" Height="36" Width="150" Text="{Binding Output}"/>
    </Grid>
برای استفاده از این UserControl در پنجره‌ی اصلی برنامه (فایل MainWindows.Xaml) به شکل زیر عمل می‌کنیم:
ابتدا فضای نام View را به فایل MainWindows.xaml اضافه می‌کنیم :
   xmlns:myview="clr-namespace:ICommnadSample.Views"
ایجاد تگ برای استفاده از View تولید شده در  Grid اصلی برنامه :
<Grid>
        <myview:CalculatorView/>
</Grid>

گام سوم :
همانطور که مشاهده می‌کنید، کنترل‌هایی که در عملیات انقیاد داده‌ها (DataBinding) شرکت می‌کنند، از طریق خاصیت Binding و معرفی خصوصیت مورد نظر مشخص می‌شوند.
برای این منظور در پوشه‌ی ViewModels و به کلاس CalculatorViewModel سه خصوصیت به‌همراه فیلد خصوصی، به شکل زیر اضافه می‌کنیم:
private double firstValue; 
private double secondValue;
private double output;
public double FirstValue
        {
            get { return firstValue; }
            set
            {
                firstValue = value;
                OnPropertyChanged("FirstValue");
            }
        }
        public double SecondValue
        {
            get { return secondValue; }
            set
            {
                secondValue = value;
                OnPropertyChanged("SecondValue");
            }
        }
        public double Output
        {
            get { return output; }
            set
            {
                output = value;
                OnPropertyChanged("Output");
            }
        }

چگونگی استفاده‌ی از اینترفیس ICommand :
برای ایجاد ارتباط بین Command ‌ها و UI می‌بایست اینترفیس ICommand توسط کلاس مورد نظر ما پیاده سازی شود. در ابتدا کلاسی با عنوان PlusCommnad  ایجاد و از اینترفیس مورد نظر ارث بری می‌کنیم.
هدف ما شبیه سازی رویداد کلیک دکمه‌ی موجود در صفحه با استفاده از Command می‌باشد.

گام چهارم:
پس از پیاده سازی اینترفیس لازم است تا کمی تغییر در بدنه متد‌ها ایجاد کنیم:
public class PlusCommand : ICommand
{
    private CalculatorViewModel calculatorViewModel;
    public PlusCommand(CalculatorViewModel vm)
    {
        calculatorViewModel = vm;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        calculatorViewModel.Add();
    }
    public event EventHandler CanExecuteChanged;
}
همانطور که می‌بینید در ابتدا فیلدی از جنس کلاس CalculatorViewModel ایجاد و از طریق سازنده‌ی کلاس آن را مقدار دهی کردیم (قصد داریم نمونه‌ای از ViewModel مورد نظر را به این کلاس ارسال کنیم).
در ادامه بصورت دستی (Hard Code) مقدار بازگردانده شده را برای تابع CanExecute به مقدار true  تغییر دادیم و متد تابع Execute را به شکلی تغییر دادیم تا تابع Add را در CalculatorViewModel، اجرا کند.

گام پنجم:
از کلاس PlusCommand در CalculatorViewModel، یک شیء ساخته و در سازنده‌ی CalculatorViewModel آن را مقدار دهی می‌کنیم: 
        private PlusCommand plusCommand;
        public CalculatorViewModel()
        {
            plusCommand = new PlusCommand(this);
        }

گام ششم:
در فایل CalculatorView ارجاعی را به فضای نام کلاس CalculatorViewModel ایجاد می‌کنیم :
   xmlns:vm="clr-namespace:ICommnadSample.ViewModels"
پس از اضافه کردن فضای نام، از تگ UserControl.Resource برای رجیستر کردن CalculatorViewModel با کلید calculatorVM جهت مشخص کردن منبع داده استفاده کردیم.
 <UserControl.Resources>
        <vm:CalculatorViewModel x:Key="calculatorVM" />
    </UserControl.Resources>

گام هفتم:
اضافه کردن تابع Add در CalculatorViewModel برای عملیات جمع :
public void Add()
{
   Output = firstValue + secondValue;
}

گام هشتم:
تعریف یک Command  برای عملیات جمع به نام AddCommand. این همان خصوصیتی است که باید بجای رویداد کلیک دکمه از طریق خاصیت Command موجود در کنترل و ویژگی Binding به کنترل متصل شود.
  public ICommand AddCommand {
            get
            {
                return plusCommand;
            }
        }

نحوه‌ی استفاده:
   Command="{Binding AddCommand}"
 
گام نهم :
برای تکمیل عملیات انقیاد داده‌ها، کلاسی به نام ViewModelBase تعریف شده است. این کلاس از اینترفیس INotifyPropertyChange ارث بری کرده و اعضای این کلاس را پیاده سازی کرده است.
  public class ViewModelBase:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
پیاده سازی این اینترفیس سبب می‌شود تا کلاس‌های ViewModel ایی که احتیاج به این اینترفیس برای عملیات انقیاد داده‌ها دارند، تنها با ارث بری، از این ظرفیت استفاده کنند و نیازی به پیاده سازی این اینترفیس در هر کلاس نباشد.

گام دهم:
ارث بری کلاس CalculatorViewModel از کلاس ViewModelBase:
   public class CalculatorViewModel : ViewModelBase
در این مرحله هر کنترلی را که قصد داریم با تغییر منبع داده بروز شود و یا با تغییر وضعیتش منبع داده تغییر کند، اعلام می‌کنیم:
   OnPropertyChanged("FirstValue");
برای هر سه خصوصیت ViewModel کد زیر را در بلاک Set تکرار می‌کنیم (توجه شود که پارامتر ارسالی باید نام پراپرتی مورد نظر باشد)
کد کامل کلاس CalculatorViewModel به شکل زیر است:
public class CalculatorViewModel : ViewModelBase
    {
        private double firstValue;
        private double secondValue;
        private double output;
        private PlusCommand plusCommand;
        public CalculatorViewModel()
        {
            plusCommand = new PlusCommand(this);
        }

        public double FirstValue
        {
            get { return firstValue; }

            set
            {
                firstValue = value;
                OnPropertyChanged("FirstValue");
            }
        }
        public double SecondValue
        {
            get { return secondValue; }
            set
            {
                secondValue = value;
                OnPropertyChanged("SecondValue");
            }
        }
        public double Output
        {

            get { return output; }

            set
            {
                output = value;
                OnPropertyChanged("Output");
            }
        }


        public ICommand AddCommand
        {
            get
            {
                return plusCommand;
            }
        }

        internal void Add()
        {
            Output = firstValue + secondValue;
        }
    }


حال می‌توانید برنامه را اجرا و تست کنید:



برای درک بهتر عملیات انقیاد دادها می‌توانید به این مقاله مراجعه کنید.

مطالب
تهیه XML امضاء شده جهت تولید مجوز استفاده از برنامه
اگر به فایل مجوز استفاده از برنامه‌‌ای مانند EF Profiler دقت کنید، یک فایل XML به ظاهر ساده بیشتر نیست:
<?xml version="1.0" encoding="utf-8"?>
<license id="17d46246-a6cb-4196-98a0-ff6fc08cb67f" expiration="2012-06-12T00:00:00.0000000" type="Trial" prof="EFProf">
  <name>MyName</name>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>b8N0bDE4gTakfdGKtzDflmmyyXI=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>IPELgc9BbkD8smXSe0sGqp5vS57CtZo9ME2ZfXSq/thVu...=</SignatureValue>
  </Signature>
</license>
در این فایل ساده متنی، نوع مجوز استفاده از برنامه، Trial ذکر شده است. شاید عنوان کنید که خوب ... نوع مجوز را به Standard تغییر می‌دهیم و فایل را ذخیره کرده و نهایتا استفاده می‌کنیم. اما ... نتیجه حاصل کار نخواهد کرد! حتی اگر یک نقطه به اطلاعات این فایل اضافه شود، دیگر قابل استفاده نخواهد بود. علت آن هم به قسمت Signature فایل XML فوق بر می‌گردد.
در ادامه به نحوه تولید و استفاده از یک چنین مجوزهای امضاء شده‌ای در برنامه‌های دات نتی خواهیم پرداخت.


تولید کلیدهای RSA

برای تهیه امضای دیجیتال یک فایل XML نیاز است از الگوریتم RSA استفاده شود.
برای تولید فایل XML امضاء شده، از کلید خصوصی استفاده خواهد شد. برای خواندن اطلاعات مجوز (فایل XML امضاء شده)، از کلیدهای عمومی که در برنامه قرار می‌گیرند کمک خواهیم گرفت (برای نمونه برنامه EF Prof این کلیدها را در قسمت Resourceهای خود قرار داده است).
استفاده کننده تنها زمانی می‌تواند مجوز معتبری را تولید کند که دسترسی به کلیدهای خصوصی تولید شده را داشته باشد.
        public static string CreateRSAKeyPair(int dwKeySize = 1024)
        {
            using (var provider = new RSACryptoServiceProvider(dwKeySize))
            {
                return provider.ToXmlString(includePrivateParameters: true);
            }
        }
امکان تولید کلیدهای اتفاقی مورد استفاده در الگوریتم RSA، در دات نت پیش بینی شده است. خروجی متد فوق یک فایل XML است که به همین نحو در صورت نیاز توسط متد provider.FromXmlString مورد استفاده قرار خواهد گرفت.


تهیه ساختار مجوز

در ادامه یک enum که بیانگر انواع مجوزهای برنامه ما است را مشاهده می‌کنید:
namespace SignedXmlSample
{
    public enum LicenseType
    {
        None,
        Trial,
        Standard,
        Personal
    }
}
به همراه کلاسی که اطلاعات مجوز تولیدی را دربر خواهد گرفت:
using System;
using System.Xml.Serialization;

namespace SignedXmlSample
{
    public class License
    {
        [XmlAttribute]
        public Guid Id { set; get; }
        
        public string Domain { set; get; }

        [XmlAttribute]
        public string IssuedTo { set; get; }

        [XmlAttribute]
        public DateTime Expiration { set; get; }

        [XmlAttribute]
        public LicenseType Type { set; get; }
    }
}
خواص این کلاس یا عناصر enum یاد شده کاملا دلخواه هستند و نقشی را در ادامه بحث نخواهند داشت؛ از این جهت که از مباحث XmlSerializer برای تبدیل وهله‌ای از شیء مجوز به معادل XML آن استفاده می‌شود. بنابراین المان‌های آن‌را مطابق نیاز خود می‌توانید تغییر دهید. همچنین ذکر ویژگی XmlAttribute نیز اختیاری است. در اینجا صرفا جهت شبیه سازی معادل مثالی که در ابتدای بحث مطرح شد، از آن استفاده شده است. این ویژگی راهنمایی است برای کلاس XmlSerializer تا خواص مزین شده با آن‌را به شکل یک Attribute در فایل نهایی ثبت کند.


تولید و خواندن مجوز دارای امضای دیجیتال

کدهای کامل کلاس تولید و خواندن یک مجوز دارای امضای دیجیتال را در اینجا مشاهده می‌کنید:
using System;
using System.IO;
using System.Security.Cryptography; // needs a ref. to `System.Security.dll` asm.
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace SignedXmlSample
{
    public static class LicenseGenerator
    {
        public static string CreateLicense(string licensePrivateKey, License licenseData)
        {
            using (var provider = new RSACryptoServiceProvider())
            {
                provider.FromXmlString(licensePrivateKey);
                var xmlDocument = createXmlDocument(licenseData);
                var xmlDigitalSignature = getXmlDigitalSignature(xmlDocument, provider);
                appendDigitalSignature(xmlDocument, xmlDigitalSignature);
                return xmlDocumentToString(xmlDocument);
            }
        }

        public static string CreateRSAKeyPair(int dwKeySize = 1024)
        {
            using (var provider = new RSACryptoServiceProvider(dwKeySize))
            {
                return provider.ToXmlString(includePrivateParameters: true);
            }
        }

        public static License ReadLicense(string licensePublicKey, string xmlFileContent)
        {
            var doc = new XmlDocument();
            doc.LoadXml(xmlFileContent);

            using (var provider = new RSACryptoServiceProvider())
            {
                provider.FromXmlString(licensePublicKey);

                var nsmgr = new XmlNamespaceManager(doc.NameTable);
                nsmgr.AddNamespace("sig", "http://www.w3.org/2000/09/xmldsig#");

                var xml = new SignedXml(doc);
                var signatureNode = (XmlElement)doc.SelectSingleNode("//sig:Signature", nsmgr);
                if (signatureNode == null)
                    throw new InvalidOperationException("This license file is not signed.");

                xml.LoadXml(signatureNode);
                if (!xml.CheckSignature(provider))
                    throw new InvalidOperationException("This license file is not valid.");

                var ourXml = xml.GetXml();
                if (ourXml.OwnerDocument == null || ourXml.OwnerDocument.DocumentElement == null)
                    throw new InvalidOperationException("This license file is coruppted.");

                using (var reader = new XmlNodeReader(ourXml.OwnerDocument.DocumentElement))
                {
                    var xmlSerializer = new XmlSerializer(typeof(License));
                    return (License)xmlSerializer.Deserialize(reader);
                }
            }
        }

        private static void appendDigitalSignature(XmlDocument xmlDocument, XmlNode xmlDigitalSignature)
        {
            xmlDocument.DocumentElement.AppendChild(xmlDocument.ImportNode(xmlDigitalSignature, true));
        }

        private static XmlDocument createXmlDocument(License licenseData)
        {
            var serializer = new XmlSerializer(licenseData.GetType());
            var sb = new StringBuilder();
            using (var writer = new StringWriter(sb))
            {
                var ns = new XmlSerializerNamespaces(); ns.Add("", "");
                serializer.Serialize(writer, licenseData, ns);
                var doc = new XmlDocument();
                doc.LoadXml(sb.ToString());
                return doc;
            }
        }

        private static XmlElement getXmlDigitalSignature(XmlDocument xmlDocument, AsymmetricAlgorithm key)
        {
            var xml = new SignedXml(xmlDocument) { SigningKey = key };
            var reference = new Reference { Uri = "" };
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
            xml.AddReference(reference);
            xml.ComputeSignature();
            return xml.GetXml();
        }

        private static string xmlDocumentToString(XmlDocument xmlDocument)
        {
            using (var ms = new MemoryStream())
            {
                var settings = new XmlWriterSettings { Indent = true, Encoding = Encoding.UTF8 };
                var xmlWriter = XmlWriter.Create(ms, settings);
                xmlDocument.Save(xmlWriter);
                ms.Position = 0;
                return new StreamReader(ms).ReadToEnd();
            }
        }
    }
}
توضیحات:
در حین کار با متد CreateLicense، پارامتر licensePrivateKey همان اطلاعاتی است که به کمک متد CreateRSAKeyPair قابل تولید است. توسط پارامتر licenseData، اطلاعات مجوز در حال تولید اخذ می‌شود. در این متد به کمک provider.FromXmlString، اطلاعات کلیدهای RSA دریافت خواهند شد. سپس توسط متد createXmlDocument، محتوای licenseData دریافتی به یک فایل XML نگاشت می‌گردد (بنابراین اهمیتی ندارد که حتما از ساختار کلاس مجوز یاد شده استفاده کنید). در ادامه متد getXmlDigitalSignature با در اختیار داشتن معادل XML شیء مجوز و کلیدهای لازم، امضای دیجیتال متناظری را تولید می‌کند. با استفاده از متد appendDigitalSignature، این امضاء را به فایل XML اولیه اضافه می‌کنیم. از این امضاء جهت بررسی اعتبار مجوز برنامه در متد ReadLicense استفاده خواهد شد.
برای خواندن یک فایل مجوز امضاء شده در برنامه خود می‌توان از متد ReadLicense استفاده کرد. توسط آرگومان licensePublicKey، اطلاعات کلید عمومی دریافت می‌شود. این کلید دربرنامه، ذخیره و توزیع می‌گردد. پارامتر xmlFileContent معادل محتوای فایل XML مجوزی است که قرار است مورد ارزیابی قرار گیرد.


مثالی در مورد نحوه استفاده از کلاس تولید مجوز

در ادامه نحوه استفاده از متدهای CreateLicense و  ReadLicense را ملاحظه می‌کنید؛ به همراه آشنایی با نمونه کلیدهایی که باید به همراه برنامه منتشر شوند:
using System;
using System.IO;

namespace SignedXmlSample
{
    class Program
    {
        static void Main(string[] args)
        {
            //Console.WriteLine(LicenseGenerator.CreateRSAKeyPair());
            writeLicense();            
            readLicense();

            Console.WriteLine("Press a key...");
            Console.ReadKey();
        }

        private static void readLicense()
        {
            var xml = File.ReadAllText("License.xml");
            const string publicKey = @"<RSAKeyValue>
                                    <Modulus>
                                        mBNKFIc/LkMfaXvLlB/+6EujPkx3vBOvLu8jdESDSQLisT8K96RaDMD1ORmdw2XNdMw/6ZBuJjLhoY13qCU9t7biuL3SIxr858oJ1RLM4PKhA/wVDcYnJXmAUuOyxP/vfvb798o6zAC1R2QWuzG+yJQR7bFmbKH0tXF/NOcSgbc=
                                    </Modulus>
                                    <Exponent>
                                        AQAB
                                    </Exponent>
                                 </RSAKeyValue>";

            var result = LicenseGenerator.ReadLicense(publicKey, xml);

            Console.WriteLine(result.Domain);
            Console.WriteLine(result.IssuedTo);
        }

        private static void writeLicense()
        {
            const string rsaData = @"<RSAKeyValue>
                    <Modulus>
                        mBNKFIc/LkMfaXvLlB/+6EujPkx3vBOvLu8jdESDSQLisT8K96RaDMD1ORmdw2XNdMw/6ZBuJjLhoY13qCU9t7biuL3SIxr858oJ1RLM4PKhA/wVDcYnJXmAUuOyxP/vfvb798o6zAC1R2QWuzG+yJQR7bFmbKH0tXF/NOcSgbc=
                    </Modulus>
                    <Exponent>
                        AQAB
                    </Exponent>
                    <P>
                        xwPKN77EcolMTD2O2Csv6k9Y4aen8UBVYjeQ4PtrNGz0Zx6I1MxLEFzRpiKC/Ney3xKg0Icwj0ebAQ04d5+HAQ==
                    </P>
                    <Q>
                        w568t0Xe6OBUfCyAuo7tTv4eLgczHntVLpjjcxdUksdVw7NJtlnOLApJVJ+U6/85Z7Ji+eVhuN91yn04pQkAtw==
                    </Q>
                    <DP>
                        svkEjRdA4WP5uoKNiHdmMshCvUQh8wKRBq/D2aAgq9fj/yxlj0FdrAxc+ZQFyk5MbPH6ry00jVWu3sY95s4PAQ==
                    </DP>
                    <DQ>
                        WcRsIUYk5oSbAGiDohiYeZlPTBvtr101V669IUFhhAGJL8cEWnOXksodoIGimzGBrD5GARrr3yRcL1GLPuCEvQ==
                    </DQ>
                    <InverseQ>
                        wIbuKBZSCioG6MHdT1jxlv6U1+Y3TX9sHED9PqGzWWpVGA+xFJmQUxoFf/SvHzwbBlXnG0DLqUvxEv+BkEid2w==
                    </InverseQ>
                    <D>                        Yk21yWdT1BfXqlw30NyN7qNWNuM/Uvh2eaRkCrhvFTckSucxs7st6qig2/RPIwwfr6yIc/bE/TRO3huQicTpC2W3aXsBI9822OOX4BdWCec2txXpSkbZW24moXu+OSHfAdYoOEN6ocR7tAGykIqENshRO7HvONJsOE5+1kF2GAE=
                    </D>
                  </RSAKeyValue>";

            string data = LicenseGenerator.CreateLicense(
                                                rsaData,
                                                new License
                                                {
                                                     Id = Guid.NewGuid(),
                                                     Domain = "dotnettips.info",
                                                     Expiration = DateTime.Now.AddYears(2),
                                                     IssuedTo = "VahidN",
                                                     Type = LicenseType.Standard
                                                });
            File.WriteAllText("License.xml", data);
        }
    }
}
ابتدا توسط متد CreateRSAKeyPair کلیدهای لازم را تهیه و ذخیره کنید. این کار یکبار باید صورت گیرد.
همانطور که مشاهده می‌کنید، اطلاعات کامل یک نمونه از آن، در متد writeLicense مورد نیاز است. اما در متد readLicense تنها به قسمت عمومی آن یعنی Modulus و Exponent نیاز خواهد بود (موارد قابل انتشار به همراه برنامه).

سؤال: امنیت این روش تا چه اندازه است؟
پاسخ: تا زمانیکه کاربر نهایی به کلیدهای خصوصی شما دسترسی پیدا نکند، امکان تولید معادل آن‌ها تقریبا در حد صفر است و به طول عمر او قد نخواهد داد!
اما ... مهاجم می‌تواند کلیدهای عمومی و خصوصی خودش را تولید کند. مجوز دلخواهی را بر این اساس تهیه کرده و سپس کلید عمومی جدید را در برنامه، بجای کلیدهای عمومی شما درج (patch) کند! بنابراین روش بررسی اینکه آیا برنامه جاری patch شده است یا خیر را فراموش نکنید. یا عموما مطابق معمول قسمتی از برنامه که در آن مقایسه‌ای بین اطلاعات دریافتی و اطلاعات مورد انتظار وجود دارد، وصله می‌شوند؛ این مورد عمومی است و منحصر به روش جاری نمی‌باشد.

دریافت نسخه جنریک این مثال:
SignedXmlSample.zip
مطالب
خودمیزبانی ماژول های Nancy
در ادامه بررسی پروژه Nancy، در این مطلب به میزبانی پروژه‌های Nancy بدون نیاز به Asp.net می‌پردازیم. به این معنی که برنامه اجرایی که شما می‌نویسید خود یک سرور ایجاد می‌کند و کاربر با وارد کردن آدرس دستگاه شما در مرورگر خود، صفحات و ماژول‌های طراحی شده توسط شما را مشاهده می‌کند.
از کاربردهای چنین سیستمی به سایت‌های قابل حمل، و یا ارائه خدمات یک نرم افزار بر روی صفحات html می‌توان اشاره کرد. مثل گوگل دسکتاپ و یا گزارشات برخی سرویس‌های ویندوزی و یا حتی تنظیم یک سخت افزار متصل به سیستم از روی شبکه. یک ایده جالب می‌تواند ارسال اس ام اس از طریق شبکه و با جی اس ام مودم باشد. که به عنوان مثال کاربران با ورود به یک صفحه و ثبت پیام بتوانند از طریق جی اس ام مودم متصل به سرور آن را ارسال کنند. با یک مثال ساده ادامه می‌دهیم.

برای شروع یک پروژه از نوع Console بسازید و در Package manager کتابخانه Nancy.Hosting.Self را نصب کنید.
حالا یک ماژول جدید به نام TestModule.cs به پروژه اضافه می‌کنیم.
public class TestModule:NancyModule
{
 public TestModule()
 {
 Get["/"] = x=> { return "It is a test for nancy self hosting."; };

 }
}

حالا وارد program.cs شده و در متدMain کد زیر را می‌نویسیم:
var selfHost = new NancyHost(new Uri("http://localhost:12345"));
selfHost.Start();
Console.ReadKey();
selfHost.Stop();

در خط اول پورتی که منتظر دریافت درخواست‌های کاربران است را برابر 12345 قرار می‌دهیم. بنابراین برای تست این کد باید در مرورگر آدرس

http://localhost:12345 را تایپ کنید. اگر بخواهیم کاربر عدد انتهایی را وارد نکند باید از پورت 80 استفاده کنیم که پیش فرض http است ولی اکثرا در سیستم برنامه نویس‌ها توسط IIS مشغول می‌باشد.
در خط بعد سرور را اجرا کرده ایم و برنامه را به حالت انتظار برای فشرده شدن کلیدی در کنسول برده ایم.
وقتی کلیدی در کنسول فشرده شود سرور به حالت توقف می‌رود و اجرای برنامه پایان می‌یابد.
Nancy امکانات دیگری هم دارد. به عنوان مثال می‌توان برای طراحی نمای ماژول‌ها از موتور‌های دید استفاده کرد (ViewEngines). موتورهایی مثل Razor و ... . در صورت علاقمندی دوستان، در این باره هم خواهم نگاشت. 
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت یازدهم - کار با فرم‌ها - قسمت دوم
در قسمت قبل، فر‌مهای template driven را بررسی کردیم. همانطور که مشاهده کردید، این نوع فرم‌ها، قابلیت‌های اعتبارسنجی پیشرفته‌ای را به همراه ندارند. برای فرم‌هایی که نیاز به اعتبارسنجی‌های سفارشی دارند، فرم‌های model driven پیشنهاد می‌شوند که در این قسمت بررسی خواهند شد.


طراحی فرم ثبت نام کاربران در سایت با روش model driven

در این قسمت قصد داریم فرم ثبت نام کاربران را به همراه اعتبارسنجی‌های پیشرفته‌ای پیاده سازی کنیم. به همین منظور، ابتدا پوشه‌ی جدید App\users را به مثال سری جاری اضافه کنید و سپس سه فایل user.ts، signup-form.component.ts و signup-form.component.html را به آن اضافه نمائید.
فایل user.ts بیانگر مدل کاربران سایت است؛ با این محتوا:
export interface IUser {
    id: number;
    name: string;
    email: string;
    password: string;
}

قالب فرم یا signup-form.component.html، در حالت ابتدایی آن چنین شکل استانداردی را خواهد داشت و فاقد اعتبارسنجی خاصی است:
<form>
    <div class="form-group">
        <label form="name">Username</label>
        <input id="name" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label form="email">Email</label>
        <input id="email" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label form="password">Password</label>
        <input id="password" type="password" class="form-control" />
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>
</form>
اکنون می‌خواهیم این فرم را به یک فرم AngularJS 2.0 ارتقاء دهیم. بنابراین نیاز است اشیاء Control و ControlGroup را ایجاد کنیم و اینبار نمی‌خواهیم AngularJS 2.0 مانند قسمت قبلی، به صورت خودکار (و ضمنی)، این اشیاء را برای ما ایجاد کند. می‌خواهیم آن‌ها را با کدنویسی (به صورت صریح) ایجاد کنیم تا بتوانیم بر روی آن‌ها کنترل بیشتری داشته باشیم.
بنابراین ابتدا کلاس کامپوننت این فرم را در فایل signup-form.component.ts به نحو ذیل تکمیل کنید:
import { Component } from '@angular/core';
import { Control, ControlGroup, Validators } from '@angular/common';
 
@Component({
    selector: 'signup-form',
    templateUrl: 'app/users/signup-form.component.html'
})
export class SignupFormComponent {
    form = new ControlGroup({
        name: new Control('', Validators.required),
        email: new Control('', Validators.required),
        password: new Control('', Validators.required)
    });
 
    onSubmit(): void {
        console.log(this.form.value);
    }
}
و همچنین پیام‌های اعتبارسنجی اولیه را نیز به نحو زیر به فایل signup-form.component.html اضافه می‌کنیم:
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
    <div class="form-group">
        <label form="name">Username</label>
        <input id="name" type="text" class="form-control"
               ngControl="name"/>
        <label class="text-danger" *ngIf="!form.controls['name'].valid">
            Username is required.
        </label>
    </div>
    <div class="form-group">
        <label form="email">Email</label>
        <input id="email" type="text" class="form-control"
               ngControl="email" #email="ngForm"/>
        <label class="text-danger" *ngIf="email.touched && !email.valid">
            Email is required.
        </label>
    </div>
    <div class="form-group">
        <label form="password">Password</label>
        <input id="password" type="password" class="form-control"
               ngControl="password" #password="ngForm"/>
        <label class="text-danger" *ngIf="password.touched && !password.valid">
            Password is required.
        </label>
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>
</form>
توضیحات:
تفاوت مهم این فرم و اعتبارسنجی‌هایش با قسمت قبل، در ایجاد اشیاء Control و ControlGroup به صورت صریح است:
form = new ControlGroup({
    name: new Control('', Validators.required),
    email: new Control('', Validators.required),
    password: new Control('', Validators.required)
});
کلا‌س‌های Control، ControlGroup و Validators در ماژول angular/common@ تعریف شده‌اند. بنابراین import متناظری نیز به ابتدای فایل اضافه شده‌است:
 import { Control, ControlGroup, Validators } from '@angular/common';

یک نکته
اگر محل قرارگیری کلاسی را فراموش کردید، آن‌را در مستندات AngularJS 2.0 ذیل قسمت API Review جستجو کنید. نتیجه‌ی جستجو، به همراه نام ماژول کلاس‌ها نیز می‌باشد.


خاصیت عمومی form که با new ControlGroup تعریف شده‌است، حاوی تعاریف صریح کنترل‌های موجود در فرم خواهد بود. در اینجا سازنده‌ی ControlGroup، یک شیء را می‌پذیرد که کلیدهای آن، همان نام کنترل‌های تعریف شده‌ی در قالب فرم و مقدار هر کدام، یک Control جدید است که پارامتر اول آن یک مقدار پیش فرض و پارامتر دوم، اعتبارسنجی مرتبطی را تعریف می‌کند (این اعتبارسنجی معرفی شده، یک متد استاتیک در کلاس توکار Validators است).
بنابراین چون سه المان ورودی، در فرم جاری تعریف شده‌اند، سه کلید جدید هم نام نیز در پارامتر ورودی ControlGroup ذکر گردیده‌اند.

اکنون که خاصیت عمومی form، در کلاس کامپوننت فوق تعریف شد، آن‌را در قالب فرم، به ngFormModel بایند می‌کنیم:
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
به این ترتیب به AngularJS 2.0 اعلام می‌کنیم که ControlGroup و Controlهای آن‌را به صورت صریح ایجاد کرده‌ایم و بجای وهله‌‌های پیش فرض خود، از خاصیت عمومی form کلاس کامپوننت، این مقادیر را تامین کن.
مراحل بعد آن، با مراحلی که در قسمت قبل بررسی کردیم، تفاوتی ندارند:
الف) در اینجا به هر المان موجود، یک ngControl نسبت داده شده‌است تا هر المان را تبدیل به یک کنترل AngularJS2 2.0 کند.
ب) به هر المان، یک متغیر محلی شروع شده با # نسبت داده شده‌است تا با اتصال آن به ngForm بتوان به ngControl تعریف شده دسترسی پیدا کرد.
البته اکنون می‌توان از خاصیت form متصل به ngFormModel نیز بجای تعریف این متغیر محلی، به نحو ذیل استفاده کرد:
 <label class="text-danger" *ngIf="!form.controls['name'].valid">
ج) از این متغیر محلی جهت نمایش یا عدم نمایش پیام‌های خطای اعتبارسنجی، در ngIfهای تعریف شده، استفاده شده‌است.
د) و در آخر متد onSumbit موجود در کلاس کامپوننت را به رخداد ngSubmit متصل کرده‌ایم. همانطور که ملاحظه می‌کنید اینبار دیگر پارامتری را به آن ارسال نکرده‌ایم. از این جهت که خاصیت form موجود در سطح کلاس، اطلاعات کاملی را از اشیاء موجود در آن دارد و در متد onSubmit کلاس، به آن دسترسی داریم.
    onSubmit(): void {
        console.log(this.form.value);
    }
this.form.value حاوی یک شیء است که تمام مقادیر پر شده‌ی فرم را به همراه دارد.

بنابراین تا اینجا تنها تفاوت فرم جدید تعریف شده با قسمت قبل، تعریف صریح ControlGroup و کنتر‌ل‌های آن در کلاس کامپوننت و اتصال آن به ngFormModel است. به این نوع فرم‌ها، فرم‌های model driven هم می‌گویند.


نمایش فرم افزودن کاربران توسط سیستم Routing

با نحوه‌ی تعریف مسیریابی‌ها در قسمت نهم آشنا شدیم. برای نمایش فرم افزودن کاربران، می‌توان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
//same as before...
import { SignupFormComponent } from './users/signup-form.component';
 
@Component({
    //same as before…
    template: `
                //same as before…                    
                <li><a [routerLink]="['AddUser']">Add User</a></li>
               //same as before…
    `,
    //same as before…
})
@RouteConfig([
    //same as before…    
    { path: '/adduser', name: 'AddUser', component: SignupFormComponent }
])
//same as before...
ابتدا به RouteConfig، مسیریابی کامپوننت فرم افزودن کاربران، اضافه شده‌است. سپس ماژول این کلاس در ابتدای فایل import شده و در آخر routerLink آن به قالب سایت و منوی بالای سایت اضافه شده‌است.


معرفی کلاس FormBuilder

روش دیگری نیز برای ساخت ControlGroup و کنترل‌های آن با استفاده از کلاس و سرویس فرم ساز توکار AngularJS 2.0 وجود دارد:
import { Control, ControlGroup, Validators, FormBuilder } from '@angular/common';

form: ControlGroup;
 
constructor(formBuilder: FormBuilder) {
    this.form = formBuilder.group({
        name: ['', Validators.required],
        email: ['', Validators.required],
        password: ['', Validators.required]
    });
}
کلاس و سرویس FormBuilder نیز در ماژول angular/common@ قرار دارد. برای استفاده‌ی از آن، آن‌را در سازنده‌ی کلاس تزریق کرده و سپس از متد group آن استفاده می‌کنیم. نحوه‌ی تعریف کلی اعضای آن با اعضای ControlGroup یکی است؛ با این تفاوت که اینبار بجای ذکر new Control، یک آرایه تعریف می‌شود که دقیقا اعضای آن، همان پارامترهای شیء کنترل هستند. این روش در کل خلاصه‌تر است و در آن تعریف چندین گروه مختلف، ساده‌تر می‌باشد. همچنین با روش تزریق وابستگی‌های بکار رفته‌ی در این فریم ورک نیز سازگاری بیشتری دارد.


پیاده سازی اعتبارسنجی سفارشی

فرض کنید می‌خواهیم ورود نام کاربر‌های دارای فاصله را غیر معتبر اعلام کنیم. برای این منظور فایل جدید usernameValidators.ts را به پوشه‌ی app\users اضافه کنید؛ با این محتوا:
import { Control } from '@angular/common';
 
export class UsernameValidators {
    static cannotContainSpace(control: Control) {
        if (control.value.indexOf(' ') >= 0) {
            return { cannotContainSpace: true };
        }
        return null;
    }
}
کلاس UsernameValidators می‌تواند شامل تمام اعتبارسنجی‌های سفارشی خاصیت نام کاربری باشد. به همین جهت نام آن جمع است و به s ختم شد‌ه‌است.
هر متد پیاده سازی کننده‌ی یک اعتبار سنجی سفارشی در این کلاس، استاتیک تعریف می‌شود؛ با نام دلخواهی که مدنظر است.
پارامتر ورودی این متدهای استاتیک، یک وهله از شیء کنترل است که توسط آن می‌توان برای مثال به خاصیت value آن دسترسی یافت و بر این اساس منطق اعتبارسنجی خود را پیاده سازی نمود. به همین جهت import آن نیز به ابتدای فایل جاری اضافه شده‌است.
خروجی این متد دو حالت دارد:
الف) اگر null باشد، یعنی اعتبارسنجی موفقیت آمیز بوده‌است.
ب) اگر اعتبارسنجی با شکست مواجه شود، خروجی این متد یک شیء خواهد بود که کلید آن، نام اعتبارسنجی مدنظر است و مقدار این کلید، هر چیزی می‌تواند باشد؛ یک true و یا یک شیء دیگر که اطلاعات بیشتری را در مورد این شکست ارائه دهد.

برای مثال اگر اعتبارسنج توکار required با شکست مواجه شود، یک چنین شی‌ءایی را بازگشت می‌دهد:
 { required:true }
و یا اگر اعتبارسنج minlength باشکست مواجه شود، اطلاعات بیشتری را در قسمت مقدار این کلید بازگشتی، ارائه می‌دهد:
{
  minlength : {
     requiredLength : 3,
     actualLength : 1
  }
}
در کل اینکه چه چیزی را بازگشت دهید، بستگی به طراحی مدنظر شما دارد.

پس از پیاده سازی یک اعتبارسنجی سفارشی، برای استفاده‌ی از آن، ابتدا ماژول آن‌را به ابتدای ماژول signup-form.component.ts اضافه می‌کنیم:
 import { UsernameValidators } from './usernameValidators';
پس از آن، شبیه به افزودن متد استاتیک توکار Validators.required، این متد جدید را به لیست اعتبارسنجی‌های خاصیت name اضافه می‌کنیم. از آنجائیکه پیشتر المان دوم این آرایه مقدار دهی شده‌است، برای ترکیب چندین اعتبارسنجی با هم، از متد Validators.compose که آرایه‌ای از متدهای اعتبارسنجی را قبول می‌کند، کمک خواهیم گرفت:
 name: ['', Validators.compose([Validators.required, UsernameValidators.cannotContainSpace])],

و مرحله‌ی آخر، نمایش یک پیام اعتبارسنجی مناسب و متناظر با متد cannotContainSpace است. برای این منظور فایل signup-form.component.html را گشوده و تغییرات ذیل را اعمال کنید:
<div class="form-group">
    <label form="name">Username</label>
    <input id="name" type="text" class="form-control"
           ngControl="name"
           #name="ngForm" />
    <div *ngIf="name.touched && name.errors">
        <label class="text-danger" *ngIf="name.errors.required">
            Username is required.
        </label>
        <label class="text-danger" *ngIf="name.errors.cannotContainSpace">
            Username can't contain space.
        </label>
    </div>
</div>
همانطور که در قسمت قبل نیز عنوان شد، چون اکنون به یک المان، بیش از یک اعتبارسنجی اعمال شده‌است، استفاده از خاصیت valid، بیش از اندازه عمومی بوده و باید از خاصیت errors استفاده کرد. به همین جهت این دو اعتبارسنجی را در یک div محصور کننده قرار می‌دهیم و در صورت وجود خطایی، خاصیت name.errors، دیگر نال نبوده و دو برچسب قرار گرفته‌ی در آن بر اساس شرط‌های ngIf آن، پردازش خواهند شد.
نام خاصیت بازگشت داده شده‌ی در اعتبارسنجی سفارشی، به عنوان یک خاصیت جدید شیء errors قابل استفاده است؛ مانند name.errors.cannotContainSpace.

به عنوان تمرین ماژول جدید emailValidators.ts را افزوده و سپس اعتبارسنجی سفارشی بررسی معتبر بودن ایمیل وارد شده را تعریف کنید:
import {Control} from '@angular/common';
 
export class EmailValidators {
    static email(control: Control) {
        var regEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        var valid = regEx.test(control.value);
        return valid ? null : { email: true };
    }
}
در ادامه آن‌را به لیست formBuilder.group افزوده و همچنین پیام اعتبارسنجی ویژه‌ای را نیز به قالب فرم اضافه کنید (کدهای کامل آن، در فایل zip انتهای بحث موجود است).


یک نکته

اگر نیاز است از regular expressions مانند مثال فوق استفاده شود، می‌توان از متد توکار Validators.pattern نیز استفاده کرد و نیازی به تعریف یک متد جداگانه برای آن وجود ندارد؛ مگر اینکه نیاز به بازگشت شیء خطای کاملتری با خواص بیشتری وجود داشته باشد.


اعتبارسنجی async یا اعتبارسنجی از راه دور (remote validation)

یک سری از اعتبارسنجی‌ها را در سمت کلاینت می‌توان تکمیل کرد؛ مانند بررسی معتبر بودن فرمت ایمیل وارده. اما تعدادی دیگر، نیاز به اطلاعاتی از سمت سرور دارند. برای مثال آیا نام کاربری در حال ثبت، تکراری است یا خیر؟ این نوع اعتبارسنجی‌ها در رده‌ی async validation قرار می‌گیرند.
سازنده‌ی شیء Control در AngularJS 2.0 که در مثال‌های بالا نیز مورد استفاده قرار گرفت، پارامتر اختیاری سومی را نیز دارد که یک AsyncValidatorFn را قبول می‌کند:
 control(value: Object, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn) : Control
پیاده سازی async validators، بسیار شبیه به سایر اعتبارسنج‌ها هستند. اما از آنجائیکه نیاز به کار با سرور را دارند، استاتیک تعریف کردن آن‌ها، سبب قطع شدن دسترسی آن‌ها از context کلاس جاری شده و امکان تزریق وابستگی‌ها را از دست خواهیم داد. برای مثال دیگر نمی‌توان به سادگی، سرویس دریافت اطلاعات کاربران را در اینجا تزریق کرد. یک راه حل رفع این مشکل، تعریف همان متد اعتبارسنج در کلاس کامپوننت فرم است:
nameShouldBeUnique(control: Control) {
    let name: string = control.value;
    return new Promise((resolve) => {
        this._userService.isUserNameUnique(<IUser>{ "name": name }).subscribe(
            (result: IResult) => {
                resolve(                    
                    result.result ? null : { 'nameShouldBeUnique': true }
                );
            },
            error => {
                resolve(null);
            }
        );
    });
}
و سپس فراخوانی آن به صورت ذیل، به عنوان سومین عنصر آرایه‌ی تعریف شده:
this.form = _formBuilder.group({
    name: ['', Validators.compose([
        Validators.required,
        UsernameValidators.cannotContainSpace
    ]),
        (control: Control) => this.nameShouldBeUnique(control)],
در اینجا با استفاده از arrow functions، امکان دسترسی به این متد تعریف شده‌ی در سطح کلاس، که استاتیک هم نیست، وجود خواهد داشت. به این ترتیب دیگر context کلاس را از دست نداده‌ایم و اینبار می‌توان به this._userService، که آن‌را در ادامه تکمیل خواهیم کرد، بدون مشکلی دسترسی یافت.
امضای متد nameShouldBeUnique تفاوتی با سایر متدهای اعتبارسنج نداشته و پارامتر ورودی آن، همان کنترل است که توسط آن می‌توان به مقدار وارد شده‌ی توسط کاربر دسترسی یافت. اما تفاوت اصلی آن در اینجا است که این متد باید یک شیء Promise را بازگشت دهد. یک Promise، بیانگر نتیجه‌ی یک عملیات async است. در اینجا دو حالت resolve و error را باید پیاده سازی کرد. در حالت error، یعنی عملیات async صورت گرفته با شکست مواجه شده‌است و در حالت resolve، یعنی عملیات تکمیل شده و اکنون می‌خواهیم نتیجه‌ی نهایی را بازگشت دهیم. نتیجه نهایی بازگشت داده شده‌ی در اینجا، همانند سایر validators است و اگر نال باشد، یعنی اعتبارسنجی موفقیت آمیز بوده و اگر یک شیء را بازگشت دهیم، یعنی اعتبارسنجی با شکست مواجه شده‌است.

این Promise، از یک سرویس تعریف شده‌ی در فایل جدید user.service.ts استفاده می‌کند:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '@angular/http';
import { IUser } from  './user';
import { IResult } from './result';
 
@Injectable()
export class UserService {
    private _checkUserUrl = '/home/checkUser';
 
    constructor(private _http: Http) { }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
 
    isUserNameUnique(user: IUser): Observable<IResult> {
        let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
        return this._http.post(this._checkUserUrl, JSON.stringify(user), options)
            .map((response: Response) => <IResult>response.json())
            .do(data => console.log("User: " + JSON.stringify(data)))
            .catch(this.handleError);
    }
}
با نحوه‌ی تعریف سرویس‌ها و همچنین کار با سرور و دریافت اطلاعات، در قسمت‌های قبلی بیشتر آشنا شدیم. در اینجا یک درخواست get، به آدرس home/checkuser سرور، ارسال می‌شود. سپس نتیجه‌ی آن در قالب اینترفیس IResult بازگشت داده خواهد شد. این اینترفیس را در فایل result.ts به صورت ذیل تعریف کرده‌ایم:
export interface IResult {
    result: boolean;
}

کدهای سمت سرور برنامه که کار بررسی یکتا بودن نام کاربری را انجام می‌دهند، به صورت ذیل در فایل Controllers\HomeController.cs تعریف شده‌اند:
namespace MVC5Angular2.Controllers
{
    public class HomeController : Controller
    {
        [HttpPost]
        public ActionResult CheckUser(User user)
        {
            var isUnique = new { result = true };
            if (user.Name?.Equals("Vahid", StringComparison.OrdinalIgnoreCase) ?? false)
            {
                isUnique = new { result = false };
            }
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(isUnique, new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
در اینجا اگر نام کاربری وارد شده مساوی Vahid بود، یک شیء anonymous، مطابق امضای اینترفیس IResult سمت کاربر (همان فایل result.ts عنوان شده) بازگشت داده می‌شود.

بنابراین تا اینجا مسیر سمت سرور home/checkuser تکمیل شده‌است. این مسیر توسط سرویس کاربر صدا زده شده و اگر نام کاربری وارد شده موجود باشد، نتیجه‌ای را مطابق امضای قرارداد IResult سفارشی ما بازگشت می‌دهد.
پس از آن مجددا به فایل signup-form.component.ts مراجعه کرده و سرویس جدید UserService را به سازنده‌ی آن تزریق کرده‌ایم. همچنین قسمت providers این کامپوننت را هم جهت تکمیل اطلاعات تزریق کننده‌ی توکار AngularJS 2.0 مقدار دهی کرده‌ایم. البته همانطور که در مبحث تزریق وابستگی‌ها نیز عنوان شد، اگر این سرویس قرار نیست در کلاس دیگری استفاده شود، نیازی نیست تا آن‌را در بالاترین سطح ممکن و در فایل app.component.ts ثبت و معرفی کرد:
@Component({
    selector: 'signup-form',
    templateUrl: 'app/users/signup-form.component.html',
    providers: [ UserService ]
})
export class SignupFormComponent {
 
    constructor(private _formBuilder: FormBuilder, private _userService: UserService) {
پس از ترزیق وابستگی private _userService: UserService، اکنون این سرویس به سادگی و به حالت متداولی در متد nameShouldBeUnique(control: Control) قابل دسترسی خواهد بود و از آن می‌توان جهت اعتبارسنجی‌های غیرهمزمان استفاده کرد.

اکنون که کدهای فعال سازی اعتبارسنجی از راه دور ما تکمیل شده‌است، به فایل signup-form.component.html مراجعه کرده و پیام مناسبی را نمایش خواهیم داد:
<div *ngIf="name.touched && name.errors">
    <label class="text-danger" *ngIf="name.errors.required">
        Username is required.
    </label>
    <label class="text-danger" *ngIf="name.errors.cannotContainSpace">
        Username can't contain space.
    </label>
    <label class="text-danger" *ngIf="name.errors.nameShouldBeUnique">
        This username is already taken.
    </label>
</div>
در ادامه اگر برنامه را اجرا کنید، با ورود نام کاربری Vahid، یک چنین پیام خطایی، مشاهده خواهد شد:



نمایش پیام loading در حین انجام اعتبارسنجی از راه دور

شاید بد نباشد که در حین انجام عملیات اعتبارسنجی از راه دور و ارسال درخواستی به سرور و بازگشت نتیجه‌ی آن، یک پیام loading را نیز نمایش داد. برای انجام این‌کار نیاز است تغییرات ذیل را به فایل signup-form.component.html اضافه کنیم:
<input id="name" type="text" class="form-control"
       ngControl="name"
       #name="ngForm" />
<div *ngIf="name.control.pending">
    Checking server, Please wait ...
</div>
در اینجا یک div جدید را ذیل المان ورود نام کاربری اضافه کرده‌ایم. همچنین نحوه‌ی نمایش آن‌را با دسترسی به متغیر name# و کنترل منتسب، به آن مدیریت می‌کنیم. اگر عملیات async ایی بر روی این کنترل در حال اجرا باشد، Promise تعریف شده، وضعیت pending را بازگشت می‌دهد. به همین جهت می‌توان از این خاصیت، جهت نمایش دادن یا مخفی کردن عبارت و یا تصویری استفاده کرد.

 
اعتبارسنجی ترکیبی در حین submit یک فرم

فرض کنید می‌خواهید منطقی را که حاصل اعتبارسنجی تمام فیلدهای فرم است (و نه هر کدام به تنهایی)، در حین submit آن اعمال کنید. برای مثال آیا ترکیب نام کاربری و کلمه‌ی عبور شخصی در حین login معتبر است یا خیر؟ در این حالت پس از بررسی‌های لازم در متد onSubmit، می‌توان با استفاده از متد find شیء form، به یکی از کنترل‌های فرم دسترسی یافت و سپس با استفاده از متد setErrors، خطای اعتبارسنجی سفارشی را به آن اضافه کرد:
onSubmit(): void {
    console.log(this.form.value);
 
    this.form.find('name').setErrors({
        invalidData : true
    }); 
}
سپس در سمت قالب این کامپوننت، نحوه‌ی نمایش این اعتبارسنجی سفارشی، همانند قبل است:
<div *ngIf="name.touched && name.errors">
    <label class="text-danger" *ngIf="name.errors.invalidData">
        Check the inputs....
    </label>
</div>


اتصال المان‌های فرم به مدلی جهت ارسال به سرور

اکنون که دسترسی به خاصیت this.form را داریم و این خاصیت توسط [ngFormModel] به تمام اشیاء تعریف شده‌ی در فرم و تغییرات آن‌ها دسترسی دارد، می‌توان از آن برای دسترسی به شیء‌ایی که حاوی مدل فرم است، استفاده کرد. برای نمونه در مثال فوق، خاصیت value آن، چنین خروجی را دارد:
  { name="VahidN", email="email@site.com", password="123"}
بنابراین برای ارسال اطلاعات این فرم به سرور، تنها کافی است این شیء را ارسال کنیم. به همین جهت در فایل user.service.ts، به کلاس سرویس کاربران، متد addUser را اضافه می‌کنیم:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '@angular/http';
import { IUser } from  './user';
import { IResult } from './result';
 
@Injectable()
export class UserService {
    private _addUserUrl = '/home/addUser';
 
    constructor(private _http: Http) { }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
 
    addUser(user: IUser): Observable<IUser> {
        let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
        return this._http.post(this._addUserUrl, JSON.stringify(user), options)
            .map((response: Response) => <IUser>response.json())
            .do(data => console.log("User: " + JSON.stringify(data)))
            .catch(this.handleError);
    }
}
کدهای سمت سرور آن در فایل Controllers\HomeController.cs نیز چنین شکلی را می‌توانند داشته باشند:
[HttpPost]
public ActionResult AddUser(User user)
{
    user.Id = 1; //todo: save user and get id from db
 
    return new ContentResult
    {
        Content = JsonConvert.SerializeObject(user, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        }),
        ContentType = "application/json",
        ContentEncoding = Encoding.UTF8
    };
}
و پس از آن کدهای متد onSubmit فایل signup-form.component.ts برای ارسال این شیء به صورت ذیل خواهند بود:
onSubmit(): void {
    console.log(this.form.value);
 
    /*this.form.find('name').setErrors({
            invalidData : true
        });*/
 
    this._userService.addUser(<IUser>this.form.value)
        .subscribe((user: IUser) => {
            console.log(`ID: ${user.id}`);
        });
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: (این کدها مطابق نگارش RC 1 هستند)
MVC5Angular2.part11.zip


خلاصه‌ی بحث

برای اینکه بتوان کنترل بیشتری را بر روی المان‌های فرم داشت، ابتدا سرویس FormBuilder را در سازنده‌ی کلاس کامپوننت فرم تزریق می‌کنیم. سپس با استفاده از متد group آن، المان‌های فرم را به صورت کلیدهای شیء پارامتر آن تعریف می‌کنیم. در اینجا می‌توان اعتبارسنجی‌های توکار AngularJS 2.0 را که در کلاس پایه‌ی Validators مانند Validators.required وجود دارند، تعریف کرد. با استفاده از متد compose آن‌ها را ترکیب نمود و یا پارامتر سومی را جهت اعتبارسنجی‌های async اضافه نمود. در این حالت شیء form تعریف شده به صورت [ngFormModel] به قالب فرم متصل می‌شود و از تغییرات آن آگاه خواهد شد.