مطالب
سیستم‌های توزیع شده در NET. - بخش هفتم- معرفی Apache Kafka
سرچشمه Kafka از LinkedIn آغاز و سپس در سال 2011 توسط Apache بصورت open source ارائه شد. هدف آن ارائه یک بستر جریان داده‌ای توزیع شده‌است که اساس آن، Publish-Subscribe می‌باشد . سادگی اضافه کردن قابلیت‌های مقیاس پذیری افقی، تحمل خطا و افزایش کارآیی توسط این بستر باعث شده‌است که هزاران شرکت از آن بعنوان بستر ارتباطی قسمتهای مختلف سیستمها و زیرسیستمهای خود استفاده کنند.
همانطور که گفته شد وظیفه و هدف اصلی Apache Kafka، ارائه یک بستر برای مدیریت و کنترل جریان‌های اطلاعاتی با کارآیی بسیار بالا، در سیستم‌ها و زیرسیستمهای مختلف است. یعنی شما می‌توانید با ایجاد کردن یک Pipeline برای جریان اطلاعات خود، وابستگی مستقیم سیستمها و زیرسیستمها را از بین ببرید؛ آن هم بصورتی که بروز مشکلی در هر قسمت، کمترین میزان تاثیر را در سایر قسمتها داشته باشد.
فرض کنید شما تعداد زیادی سیستم و زیرسیستم مختلف را داشته باشید که هر کدام از آنها نیازمند ارتباط با برخی از قسمتهای دیگر است. در این صورت شما دو راه دارید: اول اینکه در هر قسمت سرویس‌هایی را برای ارتباط با سایر قسمت‌ها پیاده سازی کنید و هر قسمت بصورت مستقیم با سایر قسمتها در ارتباط باشد.

مشخصا کنترل و مدیریت جریان اطلاعاتی در این پیاده سازی کار بسیار دشواری است. تغییر هر قسمت، تاثیر مستقیمی بر روی سایر قسمتها دارد و در صورتی که هریک از قسمتها با مشکلی روبرو شوند، سایر قسمتهای مرتبط نیز با مشکل روبرو می‌شوند. این مشکل زمانی بسیار نمایان می‌شود که در معماری‌هایی مانند میکروسرویس، بدلیل بالا رفتن تعداد زیرسیستم‌ها و ارتباطات آنها، مدیریت این ارتباطات کار بسیار دشوار، پرهزینه و پیچیده‌ای می‌شود.
روش Apache Kafka برای رفع مشکل فوق به این صورت است که Kafka با بر عهده گرفتن مدیریت ارتباطات و جریان داده‌ای قسمتهای مختلف، به شما کمک می‌کند تا تیم پیاده سازی، تنها تمرکزشان را بر روی Businessی که می‌خواهند پیاده سازی کنند، قرار دهند. با این روش می‌توانیم به راحتی سیستمهایی را پیاده سازی کنیم که از نظر ارتباطی در حالت معمول، پیچیده یا بسیار پیچیده‌اند.

همانطور که می‌بینید دیگر نیازی نیست تا قسمتهای مختلف بصورت مستقیم با یکدیگر در ارتباط باشند؛ تمامی ارتباطات از طریق Kafka انجام می‌شود. تغییر یک قسمت، تاثیر زیادی بر روی سایر قسمتها ندارد. از دسترس خارج شدن یا بروز هر گونه مشکلی در یک قسمت، بر روی کل سیستم تاثیر زیادی ندارد. پیامهای مربوط به یک قسمت تا زمانی که پردازش نشده‌اند از بین نمی‌روند. پس سیستمها می‌توانند در حالت Offline نیز به کار خود ادامه دهند. شما می‌توانید  در این روش تمامی قسمتهای  سیستم را بصورت یک Cluster پیاده سازی کنید. بنابراین احتمال از دسترس خارج شدن هر قسمت به کمترین میزان می‌رسد. اما حتی درصورتی که یک قسمت بصورت موقت از دسترس خارج شود، پیامهای مرتبط با آن قسمت تا زمانی که دوباره به جریان پردازش بازگردد، از بین نمی‌روند. پس از اضافه شدن قسمت از دسترس خارج شده، بلافاصله تمامی پیامهای مرتبط با آن قسمت برایش ارسال می‌شوند. برای بالا رفتن میزان کارآیی و تحمل خطا، به راحتی می‌توانید خود Kafka را نیز بصورت یک Cluster پیاده سازی کنید و با بالا رفتن تعداد درخواست، در صورت نیاز می‌توانید عملیات مقیاس پذیری افقی را به راحت‌ترین روش ممکن انجام دهید.

نمایی از معماری کلی Apache Kafka: 


برای شروع به آموزش Apache Kafka بهتر است ابتدا با مفاهیم و اصطلاحات آن آشنا شویم:

Producer:
  ارسال کننده پیام. Application، سیستم یا زیرسیستمی که عملیات Publish پیام را برای Topic خاص از Kafka Server انجام می‌دهد.

Consumer:
دریافت کننده پیام. Application، سیستم یا زیرسیستمی که بر روی یک یا چند Topic خاص، Subscribe کرده‌است (همچنین هر Consumer می‌تواند روی یک یا چند Partition از یک Topic خاص نیز Subscribe کند).

Consumer Group:
 گروهی از Consumer‌ها می‌باشند که با یک group.id، مشخص شده‌اند. عموما این گروه شامل یک Replicate از یک Application است؛ مانند گروه ارسال کننده ایمیل (یک زیر سیستم ارسال کننده ایمیل که چندین بار در سرور‌های مختلف اجرا شده است). Kafka این ضمانت را به ما می‌دهد که هر پیام ذخیره شده در یک Topic، برای تمامی Consumer Group‌های مرتبط ارسال شود؛ اما در هر Consumer Group، تنها یک دریافت کننده داشته باشد. یعنی هر پیام در هر Consumer Group، تنها توسط یک Consumer دریافت می‌شود.

Broker :
 قسمتی که تمامی پیامها را  از Producer دریافت می‌کند، سپس آن‌ها را در Log مربوط به Topic مشخص شده ذخیره می‌کند و پس از آن، پیام ذخیره شده را برای تمامی Consumerهای مرتبط ارسال می‌کند.

Topic: 
یک دسته بندی برای ذخیره کردن پیامهای Publish شده می‌باشد. Topicها همانند مفهوم Tableها در SQL Server می‌باشند. همانطور که می‌دانید هر Table از قبل تعریف شده‌است. یک کاربر با ارسال یک درخواست ثبت، داده‌ها را در آن ذخیره می‌کند و سپس گروهی از کاربران از داده‌های ثبت شده استفاده می‌کنند. در مفهموم Topic نیز ابتدا ما Topic مورد نظر را با خصوصیاتی که باید داشته باشد تعریف می‌کنیم (البته می‌توان بصورت Dynamic نیز آن را تعریف کرد؛ اما این روش توصیه نمی‌شود). سپس Producer پیام مربوطه را به همراه نام Topic برای Broker ارسال می‌کند. Broker پیام را در Partition مربوطه از Topic ذخیره می‌کند و سپس پیام برای تمامی Consumer‌های مربوطه ارسال می‌شود.

Partition:
یکی از تفاوتهای بسیار مهم Kafka با سایر Message broker‌ها مانند RabitMQ که باعث بالارفتن کارآیی آن نیز شده‌است، قابلیت Partition در Topic‌ها می‌باشد. در واقع هر Topic از یک یا چندین Partition برای ذخیره داده‌ها استفاده می‌کند. تعریف درست تعداد Partition‌ها در یک Topic، تاثیر مستقیمی بر درجه همزمانی و کارآیی در آن Topic و کل سیستم دارد. در Kafka تمامی پیامها به همان ترتیبی که وارد شده‌اند، در Partition‌های یک Topic ذخیره می‌شوند و به همان ترتیب نیز برای Consumer‌ها ارسال می‌شوند.
بطور مثال فرض کنید تعداد Partition‌های یک Topic با نام DepartmentMessage یک می‌باشد (از این Topic برای ذخیره پیامهای واحدهای مختلف یک سازمان استفاده می‌شود). در این صورت تمامی پیامهای دریافتی تنها در یک Partition ذخیره می‌شوند.

هر خانه از یک Partition، توسط یک شناسه از نوع int و با نام offset در دسترس است. تمامی پیامهای جدید ارسالی توسط Producer با offset ی بزرگتر از offset موجود در این Partition ذخیره می‌شوند؛ یعنی در انتهای آن قرار می‌گیرند. در مثال فوق در صورت دریافت پیام جدید، offset آن با عدد 10 مقداردهی می‌شود. همچنین عملیات خواندن نیز از کوچکترین offsetی که هنوز  مقدار آن توسط Consumer‌ها خوانده نشده‌است، انجام می‌شود. همانطور که مشخص است، بدلیل اینکه تعداد Partitionهای این مثال عدد یک می‌باشد، تمامی درخواست‌های Producer‌ها در یک Partition قرار می‌گیرند و تمامی Consumer‌ها نیز از طریق یک Partition به پیامها دسترسی دارند؛ یعنی در صورت بالا بردن تعداد Producer‌ها یا Consumer‌ها، کارآیی بالا نمی‌رود. البته با اینکه کنترل مقدار اولیه offset برای شروع یک Consumer به دست خود Consumer و Zookeeper است، اما در اکثر موارد تمامی Consumer‌های یک Topic باید از یک نقطه، شروع به خواندن داده‌ها کنند. در این حالت تا زمانیکه پیام با offset 1، توسط Consumerی خوانده نشود، هیچ Consumerی نمی‌تواند پیام شماره 2 را بخواند. استفاده کردن از یک Partition بیشتر زمانی کاربرد دارد که بخواهید تمامی پیامهایتان، واقعا در یک صف قرار بگیرند.
حال فرض کنید در سازمان شما سه واحد اداری، مالی و آموزش وجود دارد. در این صورت بدلیل اینکه تمامی پیامها در یک Partition ذخیره می‌شوند، تا زمانی که یک واحد تمامی پیامهای مرتبط با خود را از ابتدای Partition نخوانده‌است، دیگر واحدها نمی‌توانند به پیامهای مرتبط با خود دسترسی داشته باشند. پس در این صورت ما می‌توانیم تعداد Partition‌های این Topic را عدد 3 درنظر بگیریم؛ بصورتی که پیامهای مرتبط با هر واحد در یک Partition جدا قرار بگیرد.

در این روش هر Producer زمانیکه پیامی را برای این Topic ارسال می‌کند، یک Key نیز برای آن مشخص می‌کند و این Key نشان دهنده این است که پیام جدید باید در کدام Partition ذخیره شود. یعنی بصورت همزمان می‌توانید در هر سه Partition، پیامهایتان را ذخیره کنید؛ بصورتی که بطور مثال تمامی پیامهای مربوط به واحد اداری، در Partition 0  و تمامی پیامهای مربوط به واحد مالی، در Partition 1 و واحد آموزش، در Partition 2 ذخیره شوند و همچنین عملیات خواندن از این Topic نیز می‌تواند بصورت همزمان در واحدهای مختلف انجام شود.
باید در تعریف تعداد Partition‌های یک Topic این نکته را در نظر بگیرید که این تعداد کاملا به نیازمندی شما و کارآیی که شما مد نظر دارید، بستگی دارد. تعداد این Partition‌ها حتی می‌تواند به تعداد User‌های یک سیستم نیز تعریف شود. علاوه بر آن باید بدانید که هر Partition در هر زمان تنها توسط یک Primary Broker می‌تواند در دسترس سایر قسمتها قرار بگیرد و تمامی عملیات خواندن و نوشتن در Partition توسط این Kafka Server انجام می‌شود و در صورتیکه به هر دلیلی این سرور از دسترس خارج شود، مدیریت این Partition به سرور‌های دیگر داده می‌شود.

Cluster:
مجموعه ای از Brokerها می‌باشد که بصورت یک Cluster اجرا شده‌اند. این کار باعث بالا رفتن کارآیی و تحمل خطا می‌شود.

Primary Broker:
یک Kafka Server که مسئول خواندن و نوشتن در یک Partition است. در یک Cluster هر Partition در یک زمان تنها یک Primary Broker دارد. این Primary Broker همزمان می‌تواند برای Partition‌های دیگر نقش Replicas Broker را بازی کند. انتخاب یک Primary Broker برای یک Partition توسط ZooKeeper انجام می‌شود.

Replicas Brokers :
Kafka Serverهایی که شامل یک کپی از Partition می‌باشند. عملیات خواندن و نوشتن در Partition توسط Primary انجام می‌شود. در صورتیکه Primary از دسترس خارج شود، ZooKeeper یکی از Replicas Broker‌ها را بعنوان Primary در نظر می‌گیرد. همچنین این نکته را باید در نظر بگیرید که هر Replicate همزمان می‌تواند Primary پارتیشن‌های دیگر باشد.

Replication Factor :
این خصوصیت احتمال از دست دادن داده‌های یک Topic را به حداقل می‌رساند؛ به این صورت که هر پیام از یک Topic، در چندین سرور مختلف که تعداد آنها توسط این خصوصیت مشخص می‌شود، نگهداری می‌شود.

Apache ZooKeeper :
Kafka هیچ Stateی را نگه نمی‌دارد (اصطلاحا stateless می‌باشد). برای ذخیره کردن و مدیریت تمامی Stateها از جمله اینکه در حال حاضر Primary Broker برای یک Partition چه سروری است، یا اینکه پیامهای یک Partition تا کدام offset توسط Consumer‌ها خوانده شده‌اند یا اینکه کدام Consumer در حال حاضر در یک Consumer Group مسئول یک Partition می‌باشد، توسط Apache Zookeeper انجام می‌شود.

ضمانت‌هایی که Kafka می‌دهد:
  1. تمامی پیامهای دریافتی در یک Partition از یک Topic، به همان ترتیبی که دریافت می‌شوند ذخیره می‌شوند.
  2. Consumer‌ها تمامی پیامها را در یک Partition به همان ترتیبی که ذخیره شده‌اند، دریافت می‌کنند.
  3. در یک Topic با Replication Factorی با مقدار N، درجه تحمل خطا N - 1 می‌باشد.

تا اینجا با اهداف، مفاهیم و اصطلاحات Apache Kafka آشنا شدیم. در بخش بعد به راه اندازی قسمتهای مختلف آن در Ubuntu می‌پردازیم و می‌بینیم که به چه صورت می‌توان به راحتی یک Cluster از سرورهای Kafka را ایجاد کرد.
مطالب
جایگزین کردن jQuery با JavaScript خالص - قسمت دوم - کار با Attributes
عناصر HTML از سه قسمت نام، محتوا و ویژگی‌ها یا attributes تشکیل می‌شوند که دو مورد آخر، اختیاری هستند.
<form action="/rest/login" method="POST">
    <input name="username" required>
    <input type="password" name="password" required>
</form>
در این مثال سه المان form و دو input را مشاهده می‌کنید. تگ المان <form> دارای نام form و تگ المان <input> دارای نام input است.
محتوا یا content به معنای المان‌هایی هستند که درون یک المان قرار می‌گیرند. برای مثال در اینجا المان <form> دارای محتوایی متشکل از دو المان <input> است و دون المان <input> دارای محتوایی نیستند.
از ویژگی‌ها و یا attributes، برای ارائه‌ی اطلاعات بیشتر و یا تعیین حالت عناصر استفاده می‌شود. برای نمونه در اینجا المان <form> دارای دو ویژگی action و method است که برای ارائه‌ی اطلاعاتی بیشتر جهت تعیین روش و محل ارسال اطلاعات به سرور از آن‌ها استفاده می‌شود. همچنین می‌توان ویژگی‌های سفارشی را نیز توسط ویژگی‌های شروع شده‌ی با -data نیز به المان‌ها اضافه کرد که جزئی از استاندارد HTML 5 است. هرچند می‌توان ویژگی کاملا غیرمتعارفی مانند myproject-uuid را نیز به یک المان اضافه کرد. این مورد مشکل خاصی را ایجاد نکرده و صفحه بدون مشکل رندر می‌شود؛ اما یک چنین صفحه‌ای دیگر به عنوان یک صفحه‌ی استاندارد HTML ارزیابی نخواهد شد؛ از این جهت که از ویژگی استفاده کرده‌است که در هیچکدام از استانداردهای HTML ذکر نشده‌است.
آخرین نگارش HTML5 به همراه 4 ویژگی جدید دیگر نیز هست:
- Boolean attribute : مانند ویژگی required که به المان‌های <input> قابل انتساب است. حضور این نوع ویژگی‌ها بدون مقداری در یک المان
 <input name=username required>
به معنای true بودن مقدار آن‌ها است و اگر به طور کامل ذکر نشوند، مقدار false را خواهند داشت.
نمونه‌ی دیگری از این نوع ویژگی‌ها، ویژگی hidden است که به HTML 5.1 اضافه شده‌است و اگر به عنصری اضافه شود، آن المان رندر نخواهد شد.
- unquoted attribute: به این معنا که می‌توان "" را از اطراف مقدار یک ویژگی حذف کرد:
 <input name=username required>
البته با این شرط که این مقدار دارای فاصله، علامت مساوی، مقداری خالی و یا >< نباشد.
- single-quoted and double-quoted attributes: که به معنای استفاده از "" و یا '' جهت تعیین مقدار یک ویژگی است.


تفاوت attributes با خواص المان‌ها چیست؟

Attributes در تعریف تگ HTML یک عنصر ظاهر می‌شوند اما خواص، جزئی از تعریف شیء یک عنصر هستند.
<div class="bold">I'm a bold element</div>
در این مثال المان <div> دارای ویژگی class است. هرچند ویژگی‌ها و خواص دارای مفاهیم یکسانی نیستند، اما در تعریف اشیاء HTML به ازای تمام ویژگی‌های استاندارد، یک خاصیت با نام معادل نیز در نظر گرفته‌است و تغییر مقدار آن‌ها از طریق کد، سبب به روز رسانی مقدار ویژگی‌های متناظر نیز می‌شود.
 <a href="http://www.site.com/blog/">Read the blog</a>
برای مثال اگر بخواهیم مقدار ویژگی href المان فوق را تغییر دهیم، می‌توانیم ابتدا این شیء را یافته و سپس خاصیت href آن‌را به روز رسانی کنیم تا بر روی ویژگی متناظر با آن تاثیرگذار شود:
<script>
   document.querySelector('A').href = 'https://www.dntips.ir';
</script>
هرچند در اکثر موارد تناظری بین نام خواص و ویژگی‌های المان‌ها برقرار است، اما یکسری استثناءهایی هم وجود دارند:
- برای مثال واژه‌ی class در زبان جاوا اسکریپت یک واژه‌ی کلیدی است. به همین جهت در اینجا اگر بخواهیم مقدار ویژگی class را تغییر دهیم باید از خاصیت className آن المان استفاده کنیم.
- مورد دوم ویژگی checked المان‌های radio و checkbox است. این ویژگی فقط در ابتدای کار این المان‌ها به آن‌ها متصل می‌شود:
  <input type="checkbox" checked>

  <script>
      // this does not remove the checked attribute 
      document.querySelector('INPUT').checked = false;
  </script>
با اجرای قطعه کد فوق، هرچند مقدار خاصیت checked این المان false می‌شود، اما سبب حذف خود ویژگی از المان نخواهد شد.
- تمام ویژگی‌های غیراستانداردی که تعریف شوند، دارای خاصیت متناظری در آن المان نخواهند بود، اما به آن‌ها expando properties گفته می‌شود.


یافتن المان‌ها بر اساس ویژگی‌های آن‌ها

از آنجائیکه attribute selectors در استاندارد W3C CSS 2 معرفی شده‌اند، امکان کار با آن‌ها از زمان IE 8.0 میسر بوده‌است و برای کار با آ‌ن‌ها الزاما نیازی به استفاده از jQuery نیست!


یافتن المان‌ها بر اساس نام ویژگی‌ها

  <form action="/submitHandler" method="POST">
      <input name="first-name">
      <input name="last-name" required>
      <input type="email" name="email" required>
      <button disabled>submit</button>
  </form>
در اینجا برای یافتن المان‌هایی که دارای ویژگی‌هایی با نام‌های required و disabled هستند، در جی‌کوئری از CSS 2+ attribute selector string آن‌ها استفاده می‌شود:
 var $result = $('[required], [disabled]');
و در جاوا اسکریپت خالص نیز دقیقا به همان شکل و با استفاده از همان استاندارد است:
 var result = document.querySelectorAll('[required], [disabled]');
که خروجی آن آرایه‌ای از المان‌های last-name، email و دکمه است.

در اینجا باید دقت داشت که این جستجو صرفا بر اساس نام ویژگی‌ها انجام می‌شود؛ حتی اگر این ویژگی دارای مقداری نباشد:
  <div class="bold">I'm bold</div>
  <span class>I'm not</span>
در اینجا ویژگی class دوم دارای مقداری نیست و اگر کوئری ذیل را اجرا کنیم، هر دو المان span و div را دریافت خواهیم کرد:
  var result = document.querySelectorAll('[class]');


یافتن المان‌ها بر اساس نام و مقدار ویژگی‌ها

  <section>
     <h2>Sites</h2>
     <ul>
          <li>
              <a href="https://www.dntips.ir/">dotnettips</a>
          </li>
          <li>
              <a href="https://google.com/">Google</a>
          </li>
      </ul>
  </section>
اگر یافتن المان‌ها صرفا بر اساس نام ویژگی‌های آن‌ها کافی نیست، استفاده از همان روش استاندارد CSS selector string برای یافتن عنصری بر اساس نام و مقدار یک ویژگی نیز میسر است. برای مثال در این حالت در جی‌کوئری خواهیم داشت:
 var $result = $('A[href="https://www.dntips.ir/"]');
معادل این کد در جاوا اسکریپت خالص نیز به همین شکل است؛ اما بدون نیاز به وابستگی خاصی و سریعتر:
 var result = document.querySelectorAll('A[href="https://www.dntips.ir/"]');

و یا اگر اینبار بخواهیم تمام ویژگی‌های کلاسی که دارای مقدار هستند را انتخاب کنیم، روش آن با استفاده از exclusion selector به صورت زیر است:
 var result = document.querySelectorAll('[class]:not([class=""]');


یافتن المان‌ها بر اساس نام و قسمتی از مقدار ویژگی‌ها

  <a href="https://www.dntips.ir/">home page</a>
  <a href="https://www.dntips.ir/postsarchive">articles</a>
  <a href="https://www.dntips.ir/newsarchive">news</a>
در مثال قبل، المان‌هایی را که دارای مقدار ویژگی کاملا مشخصی بودند، یافتیم. اما اگر بخواهیم در قطعه HTML فوق لینک‌هایی را که دارای domain مشخصی هستند بیابیم چطور؟ در اینجا باید از substring attribute selector که جزئی از استاندارد W3C CSS 3 است، استفاده کنیم:
 var result = document.querySelectorAll('A[href*="www.dotnettips.info"]');
در اینجا تمام anchor tagهایی که دارای ویژگی href با مقداری حاوی www.dotnettips.info هستند، یافت خواهند شد.


یافتن المان‌ها بر اساس نام و کلمه‌ای مشخص در مقدار ویژگی‌ها

<div class="one two three">1 2 3</div>
<div class="onetwothree">123</div>
در این مثال می‌خواهیم المانی را بیابیم که کلاس two به آن اعمال شده‌است. برای اینکار از attribute word selector استفاده می‌شود:
 var result = document.querySelectorAll('[class∼=two]');
خروجی این کوئری، لیستی است حاوی اولین div تعریف شده.

در اینجا نوع دیگری از کوئری را هم می‌توان مطرح کرد: آیا المانی مشخص، دارای کلاس two است؟
روش انجام آن در jQuery به صورت زیر است:
 var hasTwoClass = $divEl.hasClass('two');
و در جاوا اسکریپت خالص:
 var hasTwoClass = divEl.classList.contains('two');
DOM API به همراه خاصیتی است به نام classList که امکان یافتن عنصری خاص را در آن توسط متد استاندارد contains میسر می‌کند.
همچنین خاصیت classList به همراه متدهای استاندارد add و remove نیز هست که معادل متدهای addClass و removeClass جی‌کوئری هستند.
divEl.classList.remove('red');
divEl.classList.add('blue');
و یا متد toggle خاصیت classList سبب افزوده شدن کلاسی مشخص و یا فراخوانی مجدد آن سبب حذف آن کلاس می‌شود (معادل متد toggleClass جی‌کوئری است):
// removes "hide" class
divEl.classList.toggle('hide');

// re-adds "hide" class
divEl.classList.toggle('hide');


یافتن المان‌ها بر اساس نام و شروع یا خاتمه‌ی عبارتی مشخص در مقدار ویژگی‌ها

  <img id="cat" src="/images/cat.gif">
  <img id="zebra" src="zebra.gif">
  <a href="#zebra">watch the zebra</a>
  <a href="/logout">logout</a>
در اینجا می‌خواهیم تمام المان‌هایی را که از نوع تصاویر gif هستند، به همراه لینک‌هایی که به صفحه‌ی جاری اشاره می‌کنند، بیابیم:
 var result = document.querySelectorAll('A[href^="#"], [src$=".gif"]');
این کوئری نحوه‌ی استفاده‌ی از starts with attribute value selector یا ^ و ends with attribute value selector یا $ را نمایش می‌دهد. این مثال لینک‌هایی را که با # شروع می‌شوند و همچنین المان‌هایی را که دارای src ختم شده‌ی به gif هستند، پیدا می‌کند.


خواندن مقادیر ویژگی‌ها

 <input type="password" name="user-password" required>
روش خواندن مقدار ویژگی type و بررسی وجود ویژگی required در جی‌کوئری:
// returns "password"
$inputEl.attr('type');

// returns "true"
$inputEl.is('[required]');
و معادل همین قطعه کد در جاوا اسکریپت خالص به صورت زیر است:
// returns "password"
inputEl.getAttribute('type');

// returns "true"
inputEl.hasAttribute('required');
متد getAttribute از سال 1997 به همراه استاندارد W3C DOM Level 1 Core و متد hasAttribute از سال 2000 به همراه استاندارد DOM Level 2 Core معرفی شده‌اند.


تغییر مقدار ویژگی‌ها

 <input name="temp" required>
می‌خواهیم این المان را به نحوی تغییر دهیم که نوع آن email شود، بدون ویژگی required و نام آن به userEmail تغییر یابد.
روش انجام اینکار در جی‌کوئری:
 $inputEl
 .attr('type', 'email') // #1
 .removeAttr('required') // #2
 .attr('name', 'userEmail'); // #3
و با جاوا اسکریپت خالص:
 inputEl.setAttribute('type', 'email'); // #1
inputEl.removeAttribute('required'); // #2
inputEl.setAttribute('name', 'userEmail'); // #3
متدهای استاندارد setAttribute و removeAttribute نیز جزئی از استاندارد W3C DOM Level 1 Core سال 1997 هستند؛ اما مانند jQuery قابلیت ذکر زنجیروار را ندارند که ... مهم نیست.
مطالب
ایجاد پروژه‌ی «کتابخانه» توسط Angular CLI 6.0
یکی از مواردی که با Angular CLI 6.0 به شدت ساده شده‌است، ایجاد پروژه‌های «کتابخانه» Angular است. برای مثال شاید در حین استفاده‌ی از بعضی از کتابخانه‌ی ثالث تهیه شده‌ی برای Angular با خطای ذیل مواجه شده باشید:
Please open an issue in the library repository to alert its author and ask them to 
package the library using the Angular Package Format (https://goo.gl/jB3GVv).
این خطا زمانی رخ می‌دهد که تهیه کننده‌ی کتابخانه، فرمت بسته‌های Angular را رعایت نکرده باشد و ... رعایت کردن آن نیز کار بسیار مشکلی است. نگارش 6 در پشت صحنه، پروژه‌ی موفق ng-packagr را به مجموعه‌ی CLI اضافه کرده‌است و از این پس توسط خود CLI می‌توان کتابخانه‌های استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانه‌ها‌ی npm حاصل را نیز به همراه دارد. مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار با بسته‌های فعلی npm مخصوص Angular وجود دارند؛ مانند خطایی که عنوان شد. برای مثال بدون استفاده‌ی از این ابزار، نیاز است مستندات چند صفحه‌ای ساخت کتابخانه‌های Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0 به صورت خودکار ایجاد و مدیریت می‌شود.


مراحل ایجاد یک پروژه‌ی «کتابخانه» توسط Angular CLI 6.0

مرحله‌ی اول ایجاد یک پروژه‌ی کتابخانه، مانند قبل، توسط دستور ng new و ایجاد یک پروژه‌ی دلخواه جدید است:
 ng new my-lib-test
به همراه Angular CLI 6.0، فرمت تنظیمات آن نیز تغییر کرده‌است و مفهوم workspace به آن اضافه شده‌است که در آن می‌توان چندین پروژه را تعریف کرد.
پس از ایجاد پروژه‌ی my-lib-test توسط دستور فوق و وارد شدن به پوشه‌ی اصلی آن توسط خط فرمان، می‌توان با اجرای دستور زیر، پروژه‌های دیگری را به پروژه‌ی جاری افزود:
 ng generate application my-app-name
اما اگر در اینجا بجای ذکر application، از نام library استفاده کنیم، یک کتابخانه را بجای یک برنامه، به workspace جاری اضافه می‌کند:
 ng generate library my-lib
پس از اجرای این دستور اگر به فایل angular.json دقت کنیم، این پروژه در ذیل projects اضافه شده‌است:


همچنین یک پوشه‌ی جدید به نام projects نیز ایجاد شده و پروژه‌ی my-lib داخل آن قرار گرفته‌است.


فایل جدید public_api.ts

پس از ایجاد کتابخانه‌ی جدید «my-lib»، فایل جدیدی به نام projects\my-lib\src\public_api.ts نیز به آن اضافه شده‌است:


با این محتوا:
/*
* Public API Surface of my-lib
*/
export * from './lib/my-lib.service';
export * from './lib/my-lib.component';
export * from './lib/my-lib.module';
هر خروجی که در اینجا ذکر شود توسط استفاده کنندگان از این کتابخانه قابل دسترسی خواهد بود. برای مثال دستور «ng generate library my-lib» مطابق تصویر فوق، یک سرویس جدید را به نام my-lib.service، یک کامپوننت جدید را به نام my-lib.component و یک ماژول جدید را به نام my-lib.module به صورت پیش‌فرض ایجاد کرده و درون پوشه‌ی lib قرار داده‌است. اکنون آن‌ها را توسط فایل public_api.ts، به نحوی که مشاهده می‌کنید در معرض دید استفاده کنندگان قرار می‌دهد.
برای مثال اگر فایل جدید projects\my-lib\src\lib\my-lib.models.ts را به این کتابخانه اضافه کنیم که شامل تعدادی مدل و اینترفیس قابل دسترسی توسط استفاده کنندگان باشد، باید یک سطر زیر را به انتهای فایل public_api.ts اضافه کنیم:
 export * from './lib/my-lib.models';

این پروژه‌ی کتابخانه حتی به همراه فایل‌های package.json, tsconfig.json, tslint.json مخصوص به خود نیز می‌باشد تا بتوان آن‌ها را صرفا جهت این پروژه سفارشی سازی کرد.


ساختار my-lib.service پیش‌فرض یک پروژه‌ی کتابخانه

اگر به فایل projects\my-lib\src\lib\my-lib.service.ts دقت کنیم:
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MyLibService {

  constructor() { }
}
تمام قسمت‌های آن مانند قبل است، منهای 'providedIn: 'root آن. این مورد تنظیم جدیدی است که در پروژه‌های Angular 6 قابل استفاده‌است. هدف از آن، ارائه‌ی یک سرویس، بدون نیاز به ثبت صریح آن در قسمت providers یک NgModule است.
شاید بپرسید چرا؟ هدف اصلی از آن، بهبود فرآیند tree-shaking یا حذف کدهای مرده و استفاده نشده‌است. ممکن است سرویسی را تعریف کنید، اما در برنامه استفاده نشود. این حالت خصوصا در پروژه‌های کتابخانه‌های ثالث ممکن است زیاد رخ دهد. به همین جهت با ارائه‌ی این قابلیت، امکان حذف ساده‌تر سرویس‌هایی که در برنامه استفاده نشده‌اند از خروجی نهایی کامپایل شده، وجود خواهد داشت.


چگونه به پروژه‌ی کتابخانه‌ی جدید، یک کامپوننت جدید را اضافه کنیم؟

تمام دستورات Angular CLI، در اینجا نیز کار می‌کنند. تنها تفاوت آن‌ها، ذکر صریح نام پروژه‌ی مورد استفاده است:
 ng generate component show-data --project=my-lib
دستور فوق کامپوننت جدید show-data را به پروژه‌ی my-lib اضافه خواهد کرد؛ به همراه به روز رسانی خودکار فایل projects/my-lib/src/lib/my-lib.module.ts این پروژه، جهت ثبت کامپوننت اضافه شده.
البته در اینجا باید فایل my-lib.module.ts را اندکی ویرایش کرد و ShowDataComponent را به قسمت exports نیز افزود:
@NgModule({
  imports: [
    CommonModule,
    HttpClientModule
  ],
  declarations: [MyLibComponent, ShowDataComponent],
  exports: [MyLibComponent, ShowDataComponent]
})
export class MyLibModule { }
به صورت پیش‌فرض، کامپوننت جدید را در قسمت declarations معرفی می‌کند. یک چنین کامپوننتی فقط داخل همان lib قابل استفاده‌است. اگر قرار است خارج از این lib نیز به آن دسترسی داشته باشیم، باید آن‌را در قسمت exports نیز قید کنیم.
همچنین قسمت imports آن نیز به صورت پیش‌فرض خالی است. اگر نیاز است با ngIf کار کنید، باید CommonModule را در اینجا قید کنید و اگر نیاز است تبادلات HTTP وجود داشته باشد، ذکر HttpClientModule نیز ضروری است.


مرحله‌ی ساخت پروژه

پیش از استفاده‌ی از این پروژه‌ی کتابخانه، باید آن‌را build کرد:
 ng build my-lib
در اینجا نیز دستور ng build مانند قبل است، با این تفاوت که نام پروژه‌ی کتابخانه نیز در اینجا ذکر شده‌است.
پس از اجرای این دستور، خروجی ذیل مشاهده می‌شود:
Building Angular Package
Building entry point 'my-lib'
Rendering Stylesheets
Rendering Templates
Compiling TypeScript sources through ngc
Downleveling ESM2015 sources through tsc
Bundling to FESM2015
Bundling to FESM5
Bundling to UMD
Minifying UMD bundle
Remap source maps
Relocating source maps
Copying declaration files
Writing package metadata
Removing scripts section in package.json as it's considered a potential security vulnerability.
Built my-lib
Built Angular Package!
- from: D:\my-lib-test\projects\my-lib
- to: D:\my-lib-test\dist\my-lib
همانطور که ملاحظه می‌کنید، پس از طی مراحل خاص تولید یک کتابخانه، خروجی نهایی آن‌را در پوشه‌ی dist\my-lib قرار داده‌است.


استفاده‌ی از کتابخانه‌ی تولید شده

پس از پایان موفقیت آمیز مرحله‌ی Build، اکنون نوبت به استفاده‌ی از این کتابخانه است. استفاده‌ی از آن نیز همانند تمام کتابخانه‌ها و وابستگی‌های ثالثی است که تا پیش از این از آن‌ها استفاده کرده‌ایم. برای مثال ماژول آن‌را در قسمت imports مربوط به NgModule کلاس AppModule معرفی می‌کنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و MyLibModule را به نحو ذیل اضافه می‌کنیم:
import { MyLibModule } from "my-lib";

@NgModule({
  imports: [
    BrowserModule,
    MyLibModule
  ]
})
export class AppModule { }
نکته‌ی مهمی که در اینجا باید به آن دقت داشت این است که هرچند در این پروژه، MyLibModule داخل پوشه‌ی projects\my-lib\src\lib قرار دارد، اما نباید مسیر نسبی آن‌را در اینجا ذکر کرد و باید صرفا نام پوشه‌ی my-lib واقع در پوشه‌ی node_modules را در اینجا در حین مسیر دهی import آن معرفی کرد (همانند تمام وابستگی‌های ثالث دیگر).
اما سؤال اینجا است که آیا این پوشه پس از build، داخل پوشه‌ی node_modules نیز کپی شده‌است؟ پاسخ آن خیر است و برای مدیریت خودکار آن، به صورت زیر عمل شده‌است:
اگر به فایل tsconfig.json اصلی و واقع در ریشه‌ی workspace دقت کنید، پس از اجرای دستور «ng generate library my-lib»، قسمت paths آن نیز به صورت خودکار ویرایش شده‌است:
{
  "compilerOptions": {
    "paths": {
      "my-lib": [
        "dist/my-lib"
      ]
    }
  }
}
معنای آن این است که هرگاه import ایی در برنامه به my-lib اشاره کند، کامپایلر TypeScript می‌داند که باید آن‌را از پوشه‌ی dist/my-lib دریافت و پردازش کند. به همین جهت در اینجا دیگر نیازی به کپی دستی این پوشه، به پوشه‌ی node_modules وجود ندارد.

برای نمونه اگر شاره‌گر ماوس را بر روی my-lib قرار دهید، به درستی مسیر خوانده شدن آن، تشخیص داده می‌شود.

به این ترتیب مسیر این import‌، چه در این پروژه‌ی محلی و چه برای کسانیکه پوشه‌ی dist/my-lib را به صورت یک بسته‌ی npm جدید دریافت کرده‌اند، یکی خواهد بود.

در ادامه اگر به فایل app.component.html مراجعه کرده و selector کامپوننت show-data را به آن اضافه کنیم:
 <lib-show-data></lib-show-data>
می‌توان محتویات این کامپوننت دریافت شده‌ی از کتابخانه را مشاهده کرد.


توزیع کتابخانه‌ی ایجاد شده برای عموم

برای اینکه این کتابخانه‌ی تولیدی را در اختیار عموم، در سایت npm قرار دهیم، ابتدا باید کتابخانه را در حالت production build تولید و سپس آن‌را publish کرد:
ng build my-lib --prod
cd dist/my-lib
npm publish
سطر اول، کتابخانه‌ی my-lib را در حالت production تواید می‌کند. سپس به پوشه‌ی فایل‌های نهایی تولید شده وارد می‌شویم و دستور npm publish را صادر می‌کنیم.
البته دستور آخر نیاز به ایجاد یک اکانت در سایت npm و وارد شدن به آن‌را دارد. جزئیات بیشتر آن در اینجا.
مطالب
آموزش TypeScript #5
در ادامه مباحث شی گرایی در TypeScript قصد داریم به مباحث مربوط به interface و طریقه استفاده از آن بپردازیم. همانند زبان‌های دات نتی در TypeScript نیز به راحتی می‌توانید interface تعریف کنید. در یک اینترفیس اجازه پیاده سازی هیچ تابعی را ندارید و فقط باید عنوان و پارامتر‌های ورودی و نوع خروجی آن را تعیین کنید. برای تعریف اینترفیس از کلمه کلیدی interface به صورت زیر استفاده خواهیم کرد.
export interface ILogger {
  log(message: string): void;
}
همان طور در پست‌های قبلی مشاهده شد از کلمه کلیدی export برای عمومی کردن اینترفیس استفاده می‌کنیم. یعنی این اینترفیس از بیرون ماژول خود نیز قابل دسترسی است.
حال نیاز به کلاسی داریم که این اینترفیس را پیاده سازی کند. این پیاده سازی به صورت زیر انجام می‌گیرد:
export class Logger implements ILogger
{
}
یا:
export class AnnoyingLogger implements ILogger {
   log(message: string): void{
         alert(message);
     } 
}
همانند دات نت یک کلاس می‌تواند چندین اینترفیس را پیاده سازی کند.(اصطلاحا به این روش explicit implementation یا پیاده سازی صریح می‌گویند)
export class MyClass implements IFirstInterface, ISecondInterface
{

}
*یکی از قابلیت جالب و کارآمد زبان TypeScript این است که در هنگام کار با اینترفیس‌ها حتما نیازی به پیاده سازی صریح نیست. اگر یک object تمام متغیر‌ها و توابع مورد نیاز یک اینترفیس را پیاده سازی کند به راحتی همانند روش explicit emplementation می‌توان از آن object استفاده کرد.  به این قابلیت Duck Typing  می‌گویند. مثال:
IPerson {
   firstName: string;
   lastName: string;
} 
class Person implements IPerson {
  constructor(public firstName: string, public lastName: string) {
  }
}

varpersonA: IPerson = newPerson('Masoud', 'Pakdel'); //expilict
varpersonB: IPerson = { firstName: 'Ashkan', lastName: 'Shahram'}; // duck typing
همان طور که می‌بینید object  دوم به نام personB تمام متغیر‌ها ی مورد نیاز اینترفیس IPerson را پیاده سازی کرده است در نتییجه کامپایلر همان رفتاری را که با object اول به نام personA دارد را با آن نیز خواهد داشت.

پیاده سازی چند اینترفیس به صورت همزمان
همانند دات نت که یک کلاس فقط می‌تواند از یک کلاس ارث ببرد ولی می‌تواند n  تا اینترفیس را پیاده سازی کند در TypeScript نیز چنین قوانینی وجود دارد. یعنی یک اینترفیس می‌تواند چندین اینترفیس دیگر را توسعه دهد(extend) و کلاسی که این اینترفیس را پیاده سازی می‌کند باید تمام توابع اینترفیس‌ها را پیاده سازی کند. مثال:
interface IMover {
 move() : void;
}

interface IShaker {
 shake() : void;
} 

interface IMoverShaker extends IMover, IShaker {
}
class MoverShaker implements IMoverShaker {
 move() {
 }
 shake() {
 }
}
*به کلمات کلیدی extends و implements و طریقه به کار گیری آن‌ها دقت کنید.

 instanceof

از instanceof زمانی استفاده می‌کنیم که قصد داشته باشیم که یک instance را با یک نوع مشخص مقایسه کنیم. اگر instance مربوطه از نوع مشخص باشد یا از این نوع ارث برده باشد مقدار true برگشت داده می‌شود در غیر این صورت مقدار false خواهد بود.
یک مثال:
var isLogger = logger instanceof Utilities.Logger; 
var isLogger = logger instanceof Utilities.AnnoyingLogger; 
var isLogger = logger instanceof Utilities.Formatter;
Method overriding
در TypeScript می‌توان مانند زبان‌های شی گرای دیگر Method overriding را پیاده سازی کرد. یعنی می‌توان متد‌های کلاس پایه را در کلاس مشتق شده تحریف کرد. با یک مثال به شرح این مورد خواهم پرداخت.
فرض کنید یک کلاس پایه به صورت زیر داریم:
class BaseEmployee
{   
    constructor (public fname: string,public lname: string) 
    {  
    }  
    sayInfo() 
    {  
       alert('this is base class method');
    }  
}
کلاس دیگری به نام Employee می‌سازیم که کلاس بالا را توسعه می‌دهد(یا به اصطلاح از کلاس بالا ارث می‌برد).
class Employee extends BaseEmployee
{  
   sayInfo() 
     {  
        alert('this is derived class method');
     }  
}  

window.onload = () =>  
{   
    var first: BaseEmployee= new Employee();     
    first.sayInfo();  
    var second: BaseEmployee = new BaseEmployee(); 
    second.sayInfo(); 
}
نکته مهم این است که دیگر خبری از کلمه کلیدی virtual برای مشخص کردن توابعی که قصد overriding آن‌ها را داریم نیست. تمام توابع که عمومی هستند را می‌توان override کرد.
*اگر در کلاس مشتق شده قصد داشته باشیم که به توابع و فیلد‌های کلاس پایه اشاره کنیم باید از کلمه کلیدی super استفاده کنیم.(معادل base در #C).
مثال:
class Animal {
    constructor (public name: string) {
    }
}

class Dog extends Animal {    
      constructor (public name: string, public age:number)
      {
        super(name);
      }

    sayHello() {
alert(super.name);
 } }
اگر به سازنده کلاس مشتق شده دقت کنید خواهید دید که پارامتر name را به سازنده کلاس پایه پاس دادیم: کد معادل در #C به صورت زیر است:
public class Dog : Animal 
{    
      public Dog (string name, int age):base(name)
      {
      }
}
در تابع sayHello نیز با استفاده از کلمه کلیدی super به فیلد name در کلاس پایه دسترسی خواهیم داشت.

*دقت کنید که مباحث مربوط به interface و private modifier و Type safety که پیش‌تر در مورد آن‌ها بحث شد، فقط در فایل‌های TypeScript و در هنگام کد نویسی و طراحی معنی دار هستند، زیرا بعد از کامپایل فایل‌های ts این مفاهیم در Javascript پشتیبانی نمی‌شوند در نتیجه هیچ مورد استفاده هم نخواهد داشت.

ادامه دارد...
نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
یک نکته‌ی تکمیلی: امکان بارگذاری با تاخیر فایل‌های جاوااسکریپتی در برنامه‌های Blazor

در مطلب جاری، فرض بر این است که توابع جاوا اسکریپتی، سراسری هستند و قرار است در تمام کامپوننت‌های برنامه قابل دسترسی باشند. به همین جهت ارجاع مستقیمی از فایل js. آن‌ها را در فایل index.html و یا Host_، قرار می‌دهیم. اما اگر تنها یک کامپوننت، نیاز به اسکریپت خاصی را داشته باشد و نه تمام کامپوننت‌های دیگر، چطور؟
در این حالت Blazor از مفهومی به نام JavaScript Isolation پشتیبانی می‌کند. برای توضیح آن، فایل جدید Client\wwwroot\MyMdl.Js را به پروژه اضافه کرده و سپس به صورت زیر تکمیل می‌کنیم:
export function showPrompt(message) {
  return prompt(message, "Type name");
}

export function showAlert(message) {
  return prompt(message, "Hello");
}
- همانطور که مشاهده می‌کنید، در اینجا توابع export شده‌اند (جزو پیشنیازهای JavaScript Isolation است) و در حقیقت یک ES-6 module تشکیل شده‌است.
- برخلاف قبل، مدخل جدیدی را از این فایل، به فایل‌های index.html و یا Host_  اضافه نمی‌کنیم. چون می‌خواهیم فقط کامپوننتی که به آن نیاز دارد، آن‌را بارگذاری کند.

سپس کامپوننت جدید Client\Pages\JsIsolation.razor را به صورت زیر تکمیل خواهیم کرد:
@page "/js-isolation"

@inject IJSRuntime jSRuntime

<button class="btn btn-primary" @onclick="Prompt">Prompt</button>
<button class="btn btn-primary" @onclick="ShowAlert">Alert</button>


@code
{
    private IJSObjectReference module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if(firstRender)
        {
            module = await jSRuntime.InvokeAsync<IJSObjectReference>("import", "./MyMdl.Js");
        }
    }

    private async Task Prompt()
    {
        var result = await module.InvokeAsync<string>("showPrompt", "What's your name?");
    }

    private async Task ShowAlert()
    {
        await module.InvokeVoidAsync("showAlert", "Hello!");
    }
}
- کار در قسمت OnAfterRenderAsync و در اولین بار رندر کامپوننت شروع می‌شود. در اینجا روش بارگذاری و import یک ماژول جاوااسکریپتی را مشاهده می‌کنید. در این حالت، این فایل js. پس از فراخوانی متد InvokeAsync بارگذاری شده و اطلاعات آن تنها در همین کامپوننت قابل دسترسی خواهد بود.
- اکنون که module یا IJSObjectReference را در اختیار داریم، می‌توان با استفاده از متدهای InvokeAsync و یا InvokeVoidAsync، با متدهای موجود در آن کار کرد.
مطالب
آموزش ساخت و کار با subdomain در حالت لوکال هاست

برای پروژه ای نیاز بود تا از sub-domain استفاده شود. نحوه کار بدین صورت بود که اطلاعات باید بر اساس شهر‌ها بارگذاری می‌شد؛ برای مثال اگر کاربر آدرس tehran.mysite.com را وارد کرد، اطلاعات مربوط به شهر تهران بارگذاری شود و برای سایر شهر‌ها هم به همین ترتیب. در سرور که روش کار و ساخت ساب دومین کاملا مشخص است. اما برای لوکال باید به چه ترتیب عمل شود؟

مسلما برنامه پیش از آپلود حتما باید روی لوکال تست شود. برای ساخت ساب دومین روی لوکال هاست کافی ست به مسیر زیر رفته و فایل hosts را با دسترسی administrator  باز کنید . 

[windows drive]:\Windows\System32\drivers\etc
سپس به شیوه زیر ساب دومین‌های خود را در این فایل وارد کنید .
# localhost name resolution is handled within DNS itself.
127.0.0.1  localhost
127.0.0.1 tehran.localhost
127.0.0.1 mashhad.localhost
127.0.0.1 sabzevar.localhost
خوب، از این به بعد در صورتیکه کاربر هر کدام از ساب دومین‌های وارد شده در این فایل را در مرورگر خود وارد کند، به راحتی می‌توان در رویداد Application_BeginRequest در فایل Gloabal.asax  مسیر url را بدست آورد و کاربر را به مسیر دلخواه هدایت کرد.
نکته مهمی که باید حتما توجه شود استفاده از دستور Request.Url.ToString(); است. هنگامیکه برنامه روی هاست در حال اجراست، قادر به برگرداندن مسیر به همراه با نام ساب دومین می‌باشد، اما این دستور روی لوکال، نام ساب دومین را برنمی‌گرداند! برای این کار روی لوکال استفاده از روش زیر استفاده کنید:
host = Request.Headers["HOST"].ToLower();
اکنون بعد از به دست آوردن مسیر کاربر می‌توانیم او را به مسیر دلخواه بفرستیم. مثلا من ابتدا با به دست آوردن نام شهر از روی url کاربر، کد متناظر با شهر را پیدا کرده و کد را به همراه آدرس به صفحه مورد نظر فرستاده و در آن صفحه با دریافت کد شهر، اطلاعات لازم را از دیتابیس فراخوانی می‌کنم:
void Application_BeginRequest(object sender, EventArgs e)
    {
        string host, url, newUrl;
        host = Request.Headers["HOST"].ToLower();   //for localhost
        //host = Request.Url.ToString();  //  for server
        url = Request.Url.PathAndQuery.ToLower();
        
        if (host.EndsWith(".localhost:8001") && (url == "" || url == "/"))
        {
            int cityId = City.GetCityIdByCityName(host.Replace(".localhost:8001", ""));
            newUrl = string.Format("~/postlist/{0}/", cityId);
            HttpContext.Current.RewritePath(newUrl);
            return;
        }

    }

نکته: در صورتی که در حال استفاده از vpn باشید قادر به دسترسی به subdomain‌های خود در حالت لوکال نخواهید بود. پس برای تست برنامه، حتما این مورد را مد نظر داشته باشید.

موفق باشید.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران
یک نکته‌ی تکمیلی: جایگزینی bower با npm

bower یک فناوری منسوخ شده‌است و اگر بخواهیم bower.json ذکر شده‌ی در این مطلب را با package.json مربوط به npm جایگزین کنیم، به یک چنین محتوایی خواهیم رسید:
{
  "name": "testwebapp",
  "version": "1.0.0",
  "description": "",
  "scripts": {},
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^3.3.7",
    "jquery": "^2.2.4",
    "jquery-ajax-unobtrusive": "^3.2.4",
    "jquery-validation": "^1.17.0",
    "jquery-validation-unobtrusive": "^3.2.8"
  }
}
پس از ایجاد فایل package.json فوق، کافی است دستور npm install را در ریشه‌ی پروژه وارد کنید تا این بسته‌ها دریافت و نصب شوند.
سپس بر اساس مسیرهای پوشه‌ی node_modules جدید، فایل bundleconfig.json چنین محتوایی را پیدا می‌کند:
[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "node_modules/bootstrap/dist/css/bootstrap.min.css",
      "wwwroot/css/site.css"
    ]
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "node_modules/jquery/dist/jquery.min.js",
      "node_modules/jquery-validation/dist/jquery.validate.min.js",
      "node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js",
      "node_modules/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js",
      "node_modules/bootstrap/dist/js/bootstrap.min.js",
      "wwwroot/js/site.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    },
    "sourceMap": false
  }
]
و در آخر فایل Layout.cshtml_ برنامه به این صورت ساده خواهد شد (فقط خروجی‌های نهایی css و js حاصل از BundlerMinifier در اینجا قید می‌شوند؛ که حاصل یکی کردن و فشرده سازی تمام آن‌ها است):
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - DNTCommon.Web.Core.TestWebApp</title>
    <link href="~/css/site.min.css" rel="stylesheet" asp-append-version="true" />
</head>

<body>
    <div>
        @RenderBody()
    </div>

    <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script> 
    @RenderSection("Scripts", required: false)
</body>
</html>
البته با این شرط که تنظیمات ذیل در فایل csproj برنامه موجود باشند:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <Target Name="PrecompileScript" BeforeTargets="BeforeBuild">
    <Exec Command="dotnet bundle" />
  </Target>
  <ItemGroup>
    <DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />
  </ItemGroup>
</Project>
مطالب
پیاده سازی Default Dependency Injection در AngularJS 1.x
 در این مقاله شما را با روشی آشنا خواهم کرد که در عین سادگی، کارآیی زیادی در پروژه‌های  AngularJS خواهد داشت.
برای همه ما پیش آماده است که خیلی از Injection‌های Controller‌های AngularJS به صورت مشترک بوده ولی مجبور شده‌ایم این Injection‌ها را در هر Controller تکرار کنیم. کدهای زیر را مشاهده کنید. در این کد یک سری از injection‌ها به صورت مشترک در این ۴ Controller تکرار شده است.
app.controller('ctrl1',['$scope','$rootscope','$location','$route','api','delete','notify',ctrl1]);
...
app.controller('ctrl2',['$scope','$rootscope','$location','$route','api','delete','notify','chart',ctrl2]);
...
app.controller('ctrl3',['$scope','$rootscope','$location','$route','api','delete','notify','pulling',ctrl3]);
...
app.controller('ctrl4',['$scope','$rootscope','$location','$route','api','delete','notify','chart','report',ctrl4]);
در پروژه‌های بزرگ که تعداد Controller‌ها بیش از حد می‌باشد همیشه تعدادی Injection مشترک وجود دارد و مدیریت این Injection‌ها در همه کنترل‌ها و مخصوصا کنترل‌های جدید که به پروژه اضافه میکنیم سخت می‌شود و گاهی از نظم خارج می‌شود مخصوصا وقتی که تعداد Developer‌ها در پروژه زیاد باشند.
در این روش که نوعی Trick ساده Javascript هست به شما در مدیریت Injection‌ها کمک میکند.
خوب ابتدا همه Injectionهای مشترک Controller‌ها را شناسایی می‌کنیم و یه آرایه تعریف میکنیم و همه Injection‌های مشترک را مرتب در این آرایه قرار می‌دهیم.
var injection=['$scope','$rootscope','$location','$route','api','delete','notify'];
با استفاده از تابع زیر مابقی Injection‌های مختص هر Controller را پیاده سازی می‌کنیم.
function injectionHandle() {    
    var _injection=angular.copy(injection);
    for(var i=0;i<arguments.length;i++){
        _injection.push(arguments[i]);        
    }
    return _injection;
}
توضیح تابع: در خط اول یک Copy از آرایه را تهیه می‌کنیم تا در دفعات بعد هم این آرایه قابل استفاده باشد. arguments در داخل تابع به رشته Injectionهای ارسال شده به تابع و تابع Controller اشاره دارد. با این روش می‌توانیم به صورت نامحدود Injection‌ها را به تابع ارسال کنیم. در داخل حلقه به Copy متغیر injection همه Injection‌های ارسالی به تابع را push میکنیم. نهایتا این تابع آرایه‌ای از injection‌های مشترک و Injectionهای مختص هر Controller و خود تابع Controller را بر میگرداند.
در مرحله بعدی در تعریف Controller‌ها تابع injectionHandle را همراه با Injection‌های مختص آن  Controller را صدا میزنیم.
در کد زیر نحوه استفاده از این روش را مشاهده میکنید:
app.controller('ctrl1', injectionHandle(ctrl1));
...
app.controller('ctrl2', injectionHandle('chart',ctrl2));
...
app.controller('ctrl3', injectionHandle('pulling',ctrl3));
...
app.controller('ctrl4', injectionHandle('chart','report',ctrl4));
همانطور که مشاهده کردید، با این روش خیلی راحتتر می‌توانید Injection‌های مشترک را مدیریت کنید. اگر قرار باشد Injection جدیدی را به همه Controller‌ها اضافه کنید، کافیست آن را در آرایه injection درج کنید.
مطالب
Debugging برنامه هایی که از خط فرمان پارامتر می گیرند!
بسیاری از برنامه‌ها وجود دارند که در زمان فراخوانی از خط فرمان (Command Line) پارامترهایی دریافت می‌کنند و نسبت به آن پارامترها رفتار مشخصی را از خود نشان می‌دهند.
یکی از کاربرد‌های پارامتر ورودی args که از نوع آرایه ای از رشته‌ها در متد Main برنامه‌های کنسول بطور پیش فرض تعریف شده است همین موضوع است. شما می‌توانید از طریق کنترل مقدار این پارامتر برنامه‌ی خود را توسعه دهید.
برای مثال برنامه ای جهت چاپ مجذور اعدادی که از خط فرمان خوانده می‌شوند را در نظر بگیرید. کد مورد استفاده در این برنامه به شکل زیر خواهد بود:
static void Main(string[] args)
{
    foreach (var arg in args)
    {
        var x = Convert.ToInt32(arg);
        Console.WriteLine("{0} ^ 2 = {1}", x, x * x);
    }
    Console.ReadKey();
}

اما نکته اصلی این مطلب در مورد Debugging این گونه برنامه‌ها است.
جهت دیباگ این قبیل برنامه‌ها در ویژوال استادیو از قسمت Solution Explorer بر روی نام پروژه کلیک راست کرده و گزینه Properties را انتخاب کنید.
در پنجره باز شده به زبانه Debug بروید و در قسمت Command Line Arguments پارامترهای ورودی خود را بطوریکه با فضای خالی (Space) از هم جدا شده اند وارد کنید.
 

حال می‌توانید پس از ذخیره کردن تنظیمات، کلید F5 را بزنید.
اشتراک‌ها
گوگل مجددا قابلیت خواندن و پیگیری فیدهای RSS را به کروم اضافه می‌کند

Chrome, at least in its experimental Canary version on Android (and only for users in the U.S.), is getting an interesting update in the coming weeks that brings back RSS, the once-popular format for getting updates from all the sites you love in Google Reader and similar services. 

گوگل مجددا قابلیت خواندن و پیگیری فیدهای RSS را به کروم اضافه می‌کند