مطالب
اجزاء معماری سیستم عامل اندروید (قسمت دوم معماری امنیتی اندروید) :: بخش چهارم
ایمن کردن برنامه تولید شده در برابر حملات:

هنگام دریافت اطلاعات از کاربر، باید داده‌ها در جایی ذخیره شوند. اینکه داده‌ها در کجا ذخیره و نگه داری شوند و از نفوذ به آنها جلوگیری شود، نهایت امن بودن برنامه شما را نشان میدهد. باید فرض کنید که برنامه شما به طور مستقیم یا غیرمستقیم در برخی موارد مورد حمله قرار میگیرد و تنها چیزی که بین حفاظت از اطلاعات کاربر نهایی، شما و حفاظت از داده‌ها مطرح می‌شود برعهده شما خواهد بود. چند نمونه از حملات را توضیح خواهم داد:
حملات غیر مستقیم (Indirect Attacks)
قبل از اینکه این بحث را باز کنم اجازه دهید ببینیم ترس از یک حمله تا چه حد استرس بوجود می‌آورد؟ در نیمه دوم 2010 و اوایل 2011 ، دو آسیب‌پذیری خطرناک در نسخه‌های Android به ترتیب 2.2 و 2.3 کشف شدند. آسیب‌پذیری در اصل همان خطری است که در آن یک مهاجم می‌تواند هر پرونده‌ای را که بر روی SD دستگاه ذخیره شده‌است کپی کند و به سرقت ببرد! حمله بدون اجازه از حافظه اتفاق افتاده است و آسیب پذیری، این راه را برای آن فراهم کرده است. به تصویر دقت کنید! ( این آسیب پذیری مربوط به سرقت اطلاعات است)

نکات زیر، جالب‌ترین نکات در خصوص شیوه حمله بوده که قابل توجه است:

  1. یک کاربر از یک وب سایت حاوی کدهای مخرب و آلوده که یک فایل را میزبانی می‌کند، مانند evil.html بازدید می‌کند.
  2. با توجه به یک بخش از آسیب پذیری، فایل evil.html دانلود می‌شود و کارت SD آن را بدون هشدار به کاربر، ذخیره می‌کند!
  3. با توجه به بخش دیگری از آسیب پذیری، به محض اینکه فایل ذخیره شد، می‌توان کدهای جاوا اسکریپت مخرب را روی آن اجرا کرد!
  4. بدلیل بخش پایانی حمله روی این آسیب پذیری، کدهای جاوااسکریپت تحت شرایطی خاص روی سیستم محلی (local) اجرا می‌شوند! ابزار آلوده و برنامه نویسی شده، براحتی روی کارت SD ذخیره و مستقر شده و به وب سایت مهاجم برای ارسال اطلاعات قربانی دسترسی کامل دارد.
فرض کنید که برنامه شما تمام اطلاعات ذخیره‌شده در کارت SD را برای ذخیره‌سازی، زیر نظر دسترسی‌های خودش ذخیره کرده است و مطابق با الگوی دایرکتوری‌ها جلو می‌رود. با اینحال داده‌های شما در معرض خطر سرقت قرار گرفته‌اند! این مثالی از حمله غیرمستقیم به برنامه شما است. اینکه اپلیکیشن شما تا چه میزان در برابر حملات غیرمستقیم مقاوم خواهد بود، بستگی به تلاش شما در برنامه نویسی و تحلیل موارد امنیتی دارد و لازمه آن این است که قبل از تولید نرم افزار حتما تحلیل امنیتی بعمل آید و اپلیکیشن را از آنها منع کنید.
ممکن است بپرسید، "من فقط یک توسعه دهنده اندرویدی کوچکی هستم که قصد دارم برنامه خودم را در یک مارکت اندرویدی بفروشم و قیمت این برنامه خیلی پایین است. بنابراین آیا واقعا باید زمانی را برای انجام این کار امنیتی از قبل هدر دهم؟

و من با صدای رسا جواب می‌دهم : "بله! باید این کار را انجام دهید." این کار باعث می‌شود تا در حین گسترش اپلیکیشن کوچک خود، دیگر نگران مشکلات حملات مستقیم یا غیرمستقیم نباشید.

حملات مستقیم (Direct Attacks) 

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

Proxim و ذخیره داده 

بیایید با یک مثال ساده با نام Proxim شروع کنیم: برای نوشتن یک برنامه که می‌تواند یک SMS را به افراد خاص و معین ارسال کند، قرار دادی بسته‌ایم؛ با توجه به نزدیکی به مجموعه‌ای از مختصات مکانی آنها روی GPS. برای مثال: کاربر در این برنامه می‌تواند شماره تماس همسر خود را ذخیره کرده و هر زمان که به فاصله 3 مایلی به خانه یا محل کار برسد، با او تماس میگیرد. بدین صورت همسر فرد مطلع می‌شود که او نزدیک به محل کار یا خانه است و با یک تماس تلفنی او را آگاه می‌سازد.

کدهای زیر بخشی از فایل (Save Routine, SaveController. java) هستند:

package net.zenconsult.android.controller;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import net.zenconsult.android.model.Contact;
import net.zenconsult.android.model.Location;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
public class SaveController {
 private static final String TAG = "SaveController";
 public static void saveContact(Context context, Contact contact) {
  if (isReadWrite()) {
   try {
    File outputFile = new File(context.getExternalFilesDir(null), contact.getFirstName());
    FileOutputStream outputStream = new FileOutputStream(outputFile);
    outputStream.write(contact.getBytes());
    outputStream.close();
   } catch (FileNotFoundException e) {
    Log.e(TAG, "File not found");
   } catch (IOException e) {
    Log.e(TAG, "IO Exception");
   }
  } else {
   Log.e(TAG, "Error opening media card in read/write mode!");
  }
 }
 public static void saveLocation(Context context, Location location) {
  if (isReadWrite()) {
   try {
    File outputFile = new File(context.getExternalFilesDir(null), location.getIdentifier());
    FileOutputStream outputStream = new FileOutputStream(outputFile);
    outputStream.write(location.getBytes());
    outputStream.close();
   } catch (FileNotFoundException e) {
    Log.e(TAG, "File not found");
   } catch (IOException e) {
    Log.e(TAG, "IO Exception");
   }
  } else {
   Log.e(TAG, "Error opening media card in read/write mode!");
  }
 }
 private static boolean isReadOnly() {
  Log.e(TAG, Environment
   .getExternalStorageState());
  return Environment.MEDIA_MOUNTED_READ_ONLY.equals(Environment
   .getExternalStorageState());
 }
 private static boolean isReadWrite() {
  Log.e(TAG, Environment
   .getExternalStorageState());
  return Environment.MEDIA_MOUNTED.equals(Environment
   .getExternalStorageState());
 }
}
هر بار که کاربر دکمه ذخیره مکان یا دکمه ذخیره تماس را انتخاب می‌کند، عملیات صورت میگیرد. حال بیایید به کدهای مکان و تماس نگاهی بیندازیم:

  کدهای فایل ( Location .java)

package net.zenconsult.android.model;
publicclass Location {
 private String identifier;
 privatedouble latitude;
 privatedouble longitude;
 public Location() {}
 publicdouble getLatitude() {
  return latitude;
 }
 publicvoid setLatitude(double latitude) {
  this.latitude = latitude;
 }
 publicdouble getLongitude() {
  return longitude;
 }
 publicvoid setLongitude(double longitude) {
  this.longitude = longitude;
 }
 publicvoid setIdentifier(String identifier) {
  this.identifier = identifier;
 }
 public String getIdentifier() {
  return identifier;
 }
 public String toString() {
  StringBuilder ret = new StringBuilder();
  ret.append(getIdentifier());
  ret.append(String.valueOf(getLatitude()));
  ret.append(String.valueOf(getLongitude()));
  return ret.toString();
 }
 publicbyte[] getBytes() {
  return toString().getBytes();
 }
}

کدهای فایل (Contact.java)

package net.zenconsult.android.model;
publicclass Contact {
 private String firstName;
 private String lastName;
 private String address1;
 private String address2;
 private String email;
 private String phone;
 public Contact() {}
 public String getFirstName() {
  return firstName;
 }
 publicvoid setFirstName(String firstName) {
  this.firstName = firstName;
 }
 public String getLastName() {
  return lastName;
 }
 publicvoid setLastName(String lastName) {
  this.lastName = lastName;
 }
 public String getAddress1() {
  return address1;
 }
 publicvoid setAddress1(String address1) {
  this.address1 = address1;
 }
 public String getAddress2() {
  return address2;
 }
 publicvoid setAddress2(String address2) {
  this.address2 = address2;
 }
 public String getEmail() {
  return email;
 }
 publicvoid setEmail(String email) {
  this.email = email;
 }
 public String getPhone() {
  return phone;
 }
 publicvoid setPhone(String phone) {
  this.phone = phone;
 }
 public String toString() {
  StringBuilder ret = new StringBuilder();
  ret.append(getFirstName() + "|");
  ret.append(getLastName() + "|");
  ret.append(getAddress1() + "|");
  ret.append(getAddress2() + "|");
  ret.append(getEmail() + "|");
  ret.append(getPhone() + "|");
  return ret.toString();
 }
 publicbyte[] getBytes() {
  return toString().getBytes();
 }
}
کلاس‌های مکان و تماس با استاندارد یکسانی طراحی شده‌اند و داده‌ها در محلی امن، نگه‌داری می‌شوند. هر کدام از آنها شامل ()toString و ()getBytes است که کل محتوای کلاس را به عنوان یک رشته یا مجموعه‌ای از بایت‌ها برمیگرداند.
اگر بنا بود تا به صورت دستی یک شئ تماس را اضافه کنیم، احتمالا باید به صورت زیر عمل میکردیم:
final Contact contact = new Contact();
contact.setFirstName("User 1");
contact.setLastName("L1");
contact.setAddress1("");
contact.setAddress2("");
contact.setEmail("name@site.net");
contact.setPhone("12120031337");

مطالب
طراحی یک گرید با Angular و ASP.NET Core - قسمت سوم - قالب پذیر ساختن گرید
در قسمت دوم، قالب نمایش ردیف‌های جدول، ثابت است و درون جدول به صورت مستقیمی درج و تعریف شده‌است. در ادامه می‌خواهیم این گرید را به نحوی تغییر دهیم که به ازای حالت‌های مختلفی مانند نمایش اطلاعات و یا ویرایش اطلاعات هر ردیف، از قالب‌های خاص آن‌ها استفاده شود.
قابلیتی که در ادامه از آن برای «قالب پذیر ساختن گرید» استفاده خواهیم کرد، همان نکته‌ی «امکان تعویض پویای قالب‌های یک دربرگیرنده» است که در مطلب «امکان تعریف قالب‌ها در Angular با دایرکتیو ng-template» به آن پرداختیم.


تعریف قالب‌های نمایش و ویرایش اطلاعات یک ردیف در گرید طراحی شده

پس از آشنایی با دایرکتیوهای تعریف و کار با قالب‌ها در Angular، اکنون تبدیل بدنه‌ی ثابت جدول، به دو قالب نمایش و ویرایش، ساده‌است.
در قسمت دوم این سری، کار رندر بدنه‌ی اصلی گرید توسط همین چند سطر، در قالب آن مدیریت می‌شود:
    <tbody>
      <tr *ngFor="let item of queryResult.items; let i = index">
        <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td>
        <td class="text-center">{{ item.productId }}</td>
        <td class="text-center">{{ item.productName }}</td>
        <td class="text-center">{{ item.price | number:'.0' }}</td>
        <td class="text-center">
          <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable"
            disabled="disabled" />
        </td>
      </tr>
    </tbody>
  </table>

در ادامه قسمت داخلی ngFor را تبدیل به یک ng-container می‌کنیم تا قالب پذیر شود:
    <tbody>
      <tr *ngFor="let item of queryResult.items; let i = index">
        <ng-container [ngTemplateOutlet]="loadTemplate(item)"
                 [ngOutletContext]="{ $implicit: item, idx: i }"></ng-container>
      </tr>
    </tbody>
کار دایرکتیو ngOutletContext، تنظیم شیء context هر قالب است. به این ترتیب شیء متناظر با هر ردیف و همچنین ایندکس آن‌را به هر قالب ارجاع می‌دهیم. خاصیت implicit$ به این معنا است که اگر منبع داده‌ی متغیر ورودی مشخص نشد، از مقدار item استفاده شود.
در اینجا ngTemplateOutlet این امکان را می‌دهد تا بتوان توسط کدهای برنامه، قالب هر ردیف را مشخص کرد. متد loadTemplate در کدهای کامپوننت متناظر فراخوانی شده و بر اساس وضعیت هر ردیف، یکی از دو قالب ذیل را بازگشت می‌دهد:

الف) قالب نمایش معمولی و فقط خواندنی رکوردها


<!--The Html Template for Read-Only Rows-->
<ng-template #readOnlyTemplate let-item let-i="idx">
  <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td>
  <td class="text-center">{{ item.productId }}</td>
  <td class="text-center">{{ item.productName }}</td>
  <td class="text-center">{{ item.price | number:'.0' }}</td>
  <td class="text-center">
    <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable"
      disabled="disabled" />
  </td>
  <td>
    <input type="button" value="Edit" class="btn btn-default btn-xs" (click)="editItem(item)"
    />
  </td>
  <td>
    <input type="button" value="Delete" (click)="deleteItem(item)" class="btn btn-danger btn-xs"
    />
  </td>
</ng-template>
همانطور که ملاحظه می‌کنید، در اینجا بدنه‌ی ngFor را به یک ng-template مشخص شده‌ی با readOnlyTemplate# انتقال داده‌ایم. همچنین دو متغیر ورودی item و i را توسط -let تعریف کرده‌ایم. چون عبارت منبع داده item مشخص نشده‌است، از همان خاصیت implicit$ شیء context استفاده می‌کند.
این قالب در کدهای کامپوننت آن به صورت ذیل قابل دسترسی و انتخاب شده‌است:
 @ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>;

ب) قالب ویرایش اطلاعات هر ردیف که از آن برای افزودن یک ردیف جدید هم می‌توان استفاده کرد



شبیه به همان کاری را که برای نمایش ردیف‌های فقط خواندنی انجام دادیم، در مورد قالب ویرایش هر ردیف نیز تکرار می‌کنیم. در اینجا فقط امکان ویرایش نام محصول، قیمت آن و موجود بودن آن‌را توسط یک‌سری input box مهیا کرده‌ایم:
<!--The Html Template for Editable Rows-->
<ng-template #editTemplate let-item let-i="idx">
  <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td>
  <td class="text-center">{{ item.productId }}</td>
  <td class="text-center">
    <input type="text" [(ngModel)]="selectedItem.productName" class="form-control" />
  </td>
  <td class="text-center">
    <input type="text" [(ngModel)]="selectedItem.price" class="form-control" />
  </td>
  <td class="text-center">
    <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable"
      [(ngModel)]="selectedItem.isAvailable" />
  </td>
  <td>
    <input type="button" value="Save" (click)="saveItem()" class="btn btn-success btn-xs"
    />
  </td>
  <td>
    <input type="button" value="Cancel" (click)="cancel()" class="btn btn-warning btn-xs"
    />
  </td>
</ng-template>
به این قالب نیز با توجه به template reference variable آن که editTemplate# نام دارد، به صورت ذیل در کامپوننت متناظر دسترسی خواهیم یافت.
 @ViewChild("editTemplate") editTemplate: TemplateRef<any>;

تا اینجا کار تعریف قالب‌های این گرید به پایان می‌رسد. در ادامه کدهای افزودن، ثبت، ویرایش، حذف و لغو را پیاده سازی خواهیم کرد:


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

@ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>;
@ViewChild("editTemplate") editTemplate: TemplateRef<any>;
selectedItem: AppProduct;
isNewRecord: boolean;
برای اینکه بتوانیم قالب‌ها را به صورت پویا تعویض کنیم، نیاز است در کدهای کامپوننت، به آن‌ها دسترسی داشت. اینکار را توسط تعریف ViewChildهایی با همان نام template reference variable قالب‌ها انجام داده‌ایم.
به علاوه اگر به قالب editTemplate دقت کنید، مقدار ویرایش شده به [(ngModel)]="selectedItem.productName" انتساب داده می‌شود. به همین جهت شیء selectedItem نیز تعریف شده‌است.
همچنین نیاز است بدانیم اکنون در حال ویرایش یک ردیف هستیم یا این ردیف، کاملا ردیف جدیدی است. به همین جهت پرچم isNewRecord نیز تعریف شده‌است.


فعالسازی قالب ویرایش هر ردیف

در انتهای هر ردیف، دکمه‌ی ویرایش نیز قرار دارد که به (click) آن، رخداد editItem متصل است:
editItem(item: AppProduct) {
  this.selectedItem = item;
}
در اینجا Item انتخابی را به selectedItem انتساب می‌دهیم. همین مساله سبب محاسبه‌ی مجدد ردیف می‌شود. یعنی متد loadTemplate داخل حلقه‌ی ngFor مجددا فراخوانی می‌شود:
  loadTemplate(item: AppProduct) {
    if (this.selectedItem && this.selectedItem.productId === item.productId) {
      return this.editTemplate;
    } else {
      return this.readOnlyTemplate;
    }
  }
در اینجا بررسی می‌کنیم که آیا در حال ویرایش اطلاعات هستیم؟ آیا selectedItem  مقدار دهی شده‌است و نال نیست؟ اگر بله، قالب editTemplate را بازگشت می‌دهیم. اگر خیر، قالب نمایش ردیف‌های فقط خواندنی بازگشت داده می‌شود. به این ترتیب می‌توان در کدهای برنامه به صورت پویا، در مورد نمایش قالبی خاص تصمیم‌گیری کرد.


مدیریت افزودن یک ردیف جدید

دکمه‌ی افزودن یک ردیف جدید به صورت ذیل به قالب اضافه شده‌است:
<div class="panel">
  <input type="button" value="Add new product" class="btn btn-primary" (click)="addItem()"
  />
</div>
بنابراین نیاز است رخ‌داد addItem آن‌را به صورت ذیل تعریف کرد:
  addItem() {
    this.selectedItem = new AppProduct(0, "", 0, false);
    this.isNewRecord = true;

    this.queryResult.items.push(this.selectedItem);
    this.queryResult.totalItems++;
  }
در اینجا برخلاف حالت ویرایش که selectedItem را به item انتخابی ردیف جاری تنظیم کردیم، آن‌را به یک شیء جدید و تازه تنظیم می‌کنیم. همچنین پرچم isNewRecord  را نیز true خواهیم کرد. سپس این آیتم را به لیست رکوردهای موجود گرید نیز اضافه می‌کنیم. همینقدر تغییر، سبب محاسبه‌ی مجدد loadTemplate و بارگذاری قالب ویرایشی آن می‌شود.


مدیریت لغو ویرایش هر ردیف

برای اینکه ویرایش هر ردیف را لغو کنیم و قالب آن‌‌را به حالت فقط خواندنی بازگشت دهیم، فقط کافی است selectedItem را به نال تنظیم کنیم:
cancel() {
  this.selectedItem = null;
}
با این تنظیم و محاسبه‌ی خودکار و مجدد متد loadTemplate، قسمت return this.readOnlyTemplate فعال می‌شود که سبب نمایش عادی یک ردیف خواهد شد.


مدیریت حذف هر ردیف

در اینجا با پیاده سازی متد رخ‌دادگردان deleteItem و ارسال id هر ردیف به سرور، کار حذف هر ردیف را انجام خواهیم داد:
  deleteItem(item: AppProduct) {
    this.productsService
      .deleteAppProduct(item.productId)
      .subscribe((resp: Response) => {
        this.getPagedProductsList();
      });
  }


مدیریت ثبت و یا به روز رسانی هر ردیف

آخرین عملیاتی که باید مدیریت شود، بررسی پرچم isNewRecord است. اگر true بود، کار افزودن یک ردیف جدید صورت گرفته و سپس این پرچم false می‌شود. اگر false بود، به معنای درخواست به روز رسانی ردیفی مشخص است. در پایان هر دو عملیات selectedItem را نیز true می‌کنیم و این پایان عملیات باید داخل قسمت دریافت پاسخ از سرور مدیریت شود و نه پس از فراخوانی این متدها؛ چون متدهای subscribe غیرهمزمان بوده و ردیف‌های پس از آن‌ها بلافاصله اجرا می‌شوند.
  saveItem() {
    if (this.isNewRecord) {
      this.productsService
        .addAppProduct(this.selectedItem)
        .subscribe((resp: AppProduct) => {
          this.selectedItem.productId = resp.productId;
          this.isNewRecord = false;
          this.selectedItem = null;
        });
    } else {
      this.productsService
        .updateAppProduct(this.selectedItem.productId, this.selectedItem)
        .subscribe((resp: AppProduct) => {
          this.selectedItem = null;
        });
    }
  }


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #2 - تبدیل ویژگی‌ها به نگاشت
پس از معرفی ویژگی‌های لازم، در ادامه با نحوه‌ی تبدیل این ویژگی‌ها به معادل نگاشت آن‌ها در automapper خواهم پرداخت.
متد زیر هسته‌ی اصلی عملیات است و کلیه‌ی نگاشت‌های لازم را انجام می‌دهد. این متد وظیفه‌ی تبدیل نگاشت‌ها را دارد. نگاشت‌هایی که با Attributes مشخص شده‌اند:
 public static void Initialize(Assembly assembly)
 {
     //register global convertors.
     AutoMapper.Mapper.CreateMap<DateTime, string>().ConvertUsing<DateTimeToPersianDateTimeConverter>();

     var typesToMap = from t in assembly.GetTypes()
         let attr = t.GetCustomAttribute<MapFromAttribute>()
         where attr != null
         select new {SourceType = attr.SourceType, Destination = t, Attribute = attr};

     foreach (var map in typesToMap)
     {
         AutoMapper.Mapper.CreateMap(map.SourceType, map.Destination)             
             .DoMapForMemberAttribute() // for different property names in source and destination
             .DoIgnoreMapAttribute()// ignore specified properties
             .DoUseValueResolverAttribute()// set value resolvers
             .DoIgnoreAllNonExisting()// its have to be the latest.
             ;
     } //endeach
     AutoMapper.Mapper.AssertConfigurationIsValid();
 }
ورودی این متد اسملبی مربوط به ویوومدل می‌باشد (برای زمانیکه ویوومدل‌ها در اسمبلی دیگری باشند).
در سطر اول، اقدام به رجیستر کردن کلیه‌ی مبدل‌های سراسری می‌کنیم. در این سطر مبدل تاریخ به کوچی خورشیدی مورد استفاده قرار گرفته است. سپس در اسمبلی داده شده، کلیه نوع‌هایی که ویژگی MapFromAttribute را دارند، یافته و جدا می‌کنیم. در حلقه‌ی foreach ابتدا نگاشت نوع مبدأ و مقصد را انجام می‌دهیم. خروجی این متد از نوع IMappingExpression است. گر چه این اینترفیس برای تغییر بسته است، ولی قابل توسعه می‌باشد و عملیات را توسط متدهای الحاقی انجام می‌دهیم(اصل OCP).
اگر به نحوه‌ی نامگذاری متدهای الحاقی تعریف شده دقت کرده باشید، تنها کلمه‌ی Do به ابتدای نام ویژگی‌ها اضافه شده است
.

متد الحاقی DoMapFormMemberAttribute
public static IMappingExpression DoMapForMemberAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<MapForMemberAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

     foreach (var property in ok)
     {
         expression.ForMember(property.PropertyName, 
             opt => opt.MapFrom(property.AttributeValue.MemberToMap));
     }
    return expression;
}
هر IMappingExpression دارای امکاناتی برای نگهداری و انجام فعالیت بر روی یک نگاشت می‌باشد. در کوئری ابتدای متد، کلیه‌ی پروپرتی‌هایی را که دارای ویژگی MapForMemeberAttribute می‌باشند، یافته و جدا می‌کنیم. این پروپرتی‌ها از نظر معادل اسمی در نوع مبدأ و مقصد متفاوت هستند. سپس در حلقه، کار اتصال پروپرتی مبدأ و مقصد صورت می‌گیرد.

متد الحاقی DoIgnoreMapAttribute  
public static IMappingExpression DoIgnoreAttribute(this IMappingExpression expression)
{
    foreach (var property in
        expression.TypeMap.DestinationType.GetProperties()
        .Where(x => x.GetCustomAttribute<IgnoreMapAttribute>() != null))
    {
        expression.ForMember(property.Name, opt => opt.Ignore());
    }
    return expression;
}
این متد کلیه‌ی پروپرتی‌هایی را که دارای ویژگی IgnoreMapAttribute باشند، از گردونه‌ی نگاشت automapper خارج می‌کند. به عنوان مثال پروپرتی Password در ویوومدل مربوط به تغییر گذرواژه را نظر بگیرید. این پروپرتی نباید مقدار معادلی در شیء EF داشته باشد. از طرفی هم باید در ویوو وجودداشته باشد. با استفاده از این ویژگی هیچ نگاشتی انجام نمی‌شود و می‌توان تضمین کرد که گذرواژه به ویوومدل و ویوو راه پیدا نمی‌کند.

متد الحاقی DoUseValueResolverAttribute 
public static IMappingExpression DoUseValueResolverAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<UseValueResolverAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

    foreach (var property in ok)
    {
        expression.ForMember(property.PropertyName,
            opt => opt.ResolveUsing(property.AttributeValue.ValueResolver));
    }
    return expression;
}
به شیوه‌ی قبل، ابتدا نوع هایی را که دارای ویژگی UseValueResolverAttribute باشند، یافته و جدا می‌کنیم. سپس در حلقه، کار نگاشت متناظر در automapper انجام می‌گیرد. لازم به ذکر است که متد opt.ResolveUsing یک شیء با کارآیی (can do) اینترفیس IValueResolver را به عنوان آرگومان می‌گیرد.

متد الحاقی DoIgnoreAllNonExisting  
public static IMappingExpression DoIgnoreAllNonExisting(this IMappingExpression expression)
{
    var attr = expression.TypeMap.DestinationType.GetCustomAttribute<MapFromAttribute>();
    
    if (attr?.IgnoreAllNonExistingProperty == false)//instead of if(attr == null || attr.IgnoreAllNonExistingProperty == false)
        return expression;
    
    foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}
این متد برحسب پرچم تعیین شده در هنگام بکارگیری ویژگی MapFromAttribute رفتار می‌کند. به این صورت که اگر موقع تعریف، مقدار IgnoreAllNonExistingProperty را صحیح اعلام کنیم، تمام پروپرتی‌های مقصد را که معادل اسمی در مبدأ نداشته باشند و همچنین هیچگونه تنظیمی جهت مشخص سازی تکلیف نگاشت آن‌ها صورت نگرفته باشد، از گردونه‌ی نگاشت Automapper خارج می‌کند.

توضیح تکمیلی:
پس از تنظیم کلیه‌ی نگاشت‌ها در automapper جهت اطمینان از صحت تنظیمات، فراخوانی متد AutoMapper.Mapper.AssertConfigurationIsValid الزامی است. یکی از عواملی که باعث شکست این متد می‌شود، وجود پروپرتی‌هایی در نوع مقصد است، بطوریکه معادل اسمی در نوع مبدأ نداشته باشند و یا تنظیمی جهت مشخص سازی نگاشت آن انجام نشده باشد (پروپرتی که قابل نگاشت نباشد). در حقیقت این شکست بسیار مفید است. به این صورت که اگر این شکست صورت نگیرد در حین نگاشت مقادیر، باید از null یا مقدار default بدون اطلاع برنامه نویس برای مقداردهی پروپرتی استفاده کند و این یک حالت نامعلوم شیء است. اگر می‌خواهید این پروپرتی‌ها مقدار پیشفرضی بگیرند و همچنین باعث شکست عملیات هم نشوند، باید بطور صریح این موضوع را اعلام کنید. این اعلام یا باید به همین روش صورت بگیرد یا باید از ویژگی IgnorMapAttribute استفاده شود. تنها تفاوت این دو، نحوه‌ی اعمال تنظیم می‌باشد. IgnorMapAttribute باید روی تک تک پروپرتی‌های مدنظر قرار گیرد، ولی در روش اول تنها کافیست که مقدار true تنظیم گردد. به‌نظر استفاده از IgnoreMapAttribute باعث طولانی شدن کدها می‌شود؛ اما توصیه می‌شود که از همین شیوه استفاده کنید.

تا اینجا کدهای مورد نیاز نوشته شدند. در ادامه به ارائه‌ی یک مثال برای نگاشت اشیاء در Automapper توسط Attributeها می‌پردازم.
مدل ساده‌ی زیر را در نظر بگیرید:
public class Student
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual string Family { set; get; }
    public virtual string Email { set; get; }
    public virtual DateTime RegisterDateTime { set; get; }
    public virtual ICollection<Book> Books { set; get; }
}
public class Book
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual DateTime BorrowDateTime { set; get; }
    public virtual DateTime ExpiredDateTime { set; get; }
    public virtual decimal Price { set; get; }
    [ForeignKey("StudentIdFk")]
    public virtual Student Student { set; get; }
    public virtual int StudentIdFk { set; get; }
}
با ویوومدل متناظر ذیل:
[MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
public class AdminStudentViewModel
{
    // [IgnoreMap]
    public int Id { set; get; }

    [MapForMember("Name")]
    public string FirstName { set; get; }

    [MapForMember("Family")]
    public string LastName { set; get; }

 [IgnoreMap]  public string Email { set; get; } [MapForMember("RegisterDateTime")] public string RegisterDateTimePersian { set; get; } [UseValueResolver(typeof (BookCountValueResolver))] public int BookCounts { set; get; } [UseValueResolver(typeof (BookPriceValueResolver))] public decimal TotalBookPrice { set; get; } };
در تنظیم ویژگی MapFromAttribute ابتدا نوع مبدأ (Student) را مشخص کردیم و بعد صراحتاً گفتیم که از نگاشت پروپرتی‌های بلاتکلیف صرف نظر کند و همچنین پرچم انتقال Data Annotation‌های EF به ویوومدل را هم برافراشتیم. توسط MapForMember پروپرتی FirstName را به پروپرتی Name در مبدأ تنظیم کردیم و LastName را به Family. همچنین Email را بصورت صریح از نگاشت شدن منع کردیم. پروپرتی BookCounts تعداد کتاب‌ها را محاسبه می‌کند و TotalBookPrice قیمت کلیه‌ی کتاب‌ها را. برای این موارد از تأمین کننده‌ی داده (Value Resolver) استفاده کردیم. این تأمین کننده‌ها می‌توانند اینچنین پیاده سازی شوند:
public class BookCountValueResolver : ValueResolver<Student, int>
{
    protected override int ResolveCore(Student source) => source.Books.Count;
};
public class BookPriceValueResolver : ValueResolver<Student, decimal>
{
    protected override decimal ResolveCore(Student source) => source.Books.Sum(b => b.Price);
};
نحوه‌ی پیکربندی و مشاهده‌ی نتایج را در یک برنامه‌ی تحت کنسول پیاده سازی کردم. متد Main آن می‌تواند اینچنین باشد:
static void Main(string[] args)
{
    var assemblyToLoad = Assembly.GetAssembly(typeof (AdminStudentViewModel));//get assembly
    global::AttributesForAutomapper.Configuration.Initialize(assemblyToLoad);//init automaper
    IList<Student> lst;
    using (var context = new MySampleContext())
    {
        lst = context.Students.Include(x => x.Books).ToList();
    }
    foreach (var student in lst)
        {
            WriteLine( $"[{student.Id}]*\n{student.Name} {student.Family}.\nmailto:{student.Email}.\nRegistered at'{student.RegisterDateTime}'");
            foreach (var book in student.Books)
                WriteLine($"\tBook name:{book.Name}, Book price:{book.Price}");
        }
    
    var lstViewModel = AutoMapper.Mapper.Map<IList<Student>, IList<AdminStudentViewModel>>(lst);
    foreach (var adminStudentViewModel in lstViewModel)
    {
        WriteLine(
            $"[{adminStudentViewModel.Id}]*\n\t{adminStudentViewModel.FirstName} {adminStudentViewModel.LastName}.\n\t" +
            $"mailto:{adminStudentViewModel.Email}.\n\tRegistered at'{adminStudentViewModel.RegisterDateTimePersian}'\n\t" +
            $"Book Counts: {adminStudentViewModel.BookCounts} with total price of {adminStudentViewModel.TotalBookPrice}");
    }
    WriteLine("Press any key to exit...");
    ReadKey();
}
ابتدا اسمبلی مربوط به ویوومدل‌ها را مشخص می‌کنیم. سپس این اسمبلی را جهت تبدیل ویژگی‌ها به نگاشت‌های معتبر automapper به متد Initialize ارسال می‌کنیم. تنها بکار بردن همین دوسطر برای اعمال تنظیم‌ها مورد نیاز می‌باشد. بعد از اجرای موفق متد Initialize، نگاشت‌های اشیاء آماده هستند.
نمونه‌ی خروجی:
[1]*
Morteza Raeisi.
mailto:MrRaeisi@outlook.com.
Registered at'23/08/1392 19:11:43'    // I'm using Windows 10 with Persian calendar as default, On other OS or calendar settings, this value is different.
        Book name:AutoMapper Attr, Book price:1000.00
        Book name:Second Book, Book price:2500.00
        Book name:Hungry Book, Book price:2500.00
...
[1]*
Morteza Raeisi. //MapForMemebers
mailto:.  // IgnoreMap
Registered at'1392/08/23 19:11' // Convert using
Book Counts: 3 with total price of 6000.00  // Value resolvers
...
دریافت کدها + مثال
مطالب
نگاهی به هویت سنجی کاربران در ASP.NET MVC 5
در مقاله پیش رو، سعی شده‌است به شکلی تقریبا عملی، کلیاتی در مورد Authentication در MVC5 توضیح داده شود. هدف روشن شدن ابهامات اولیه در هویت سنجی MVC5 و حل شدن مشکلات اولیه برای ایجاد یک پروژه است.
در MVC 4 برای دسترسی به جداول مرتبط با اعتبار سنجی (مثلا لیست کاربران) مجبور به استفاده از متدهای از پیش تعریف شده‌ی رفرنس‌هایی که برای آن نوع اعتبار سنجی وجود داشت، بودیم. راه حلی نیز برای دسترسی بهتر وجود داشت و آن هم ساختن مدل‌های مشابه آن جدول‌ها و اضافه کردن چند خط کد به برنامه بود. با اینکار دسترسی ساده به Roles و Users برای تغییر و اضافه کردن محتوای آنها ممکن می‌شد. در لینک زیر توضیحاتی در مورد روش اینکار وجود دارد.
 در MVC5 داستان کمی فرق کرده است. برای درک موضوع پروژه ای بسازید و حالت پیش فرض آن را تغییر ندهید و آن را اجرا کنید و ثبت نام را انجام دهید، بلافاصله تصویر زیر در دیتابیس نمایان خواهد شد.

دقت کنید بعد از ایجاد پروژه در MVC5 دو پکیج بصورت اتوماتیک از طریق Nuget به پروژه شما اضافه میشود:
 Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.EntityFrameWork
عامل اصلی تغییرات جدید، همین دو پکیج فوق است.
 اولین پکیج شامل اینترفیس‌های IUser و IRole است که شامل فیلدهای مرتبط با این دو می‌باشد. همچنین اینترفیسی به نام IUserStore وجود دارد که چندین متد داشته و وظیفه اصلی هر نوع اضافه و حذف کردن یا تغییر در کاربران، بر دوش آن است.
 دومین پکیج هم وظیفه پیاده سازی آن‌چیزی را دارد که در پکیج اول معرفی شده است. کلاس‌های موجود در این پکیج ابزارهایی برای ارتباط EntityFramework با دیتابیس هستند.
اما از مقدمات فوق که بگذریم برای درک بهتر رفتار با دیتابیس یک مثال را پیاده سازی خواهیم کرد.

 فرض کنید میخواهیم چنین ارتباطی را بین سه جدول در دیتابیس برقرار کنیم، فقط به منظور یادآوری، توجه کنید که جدول ASPNetUsers جدولی است که به شکل اتوماتیک پیش از این تولید شد و ما قرار است به کمک یک جدول واسط (AuthorProduct) آن را به جدول Product مرتبط سازیم تا مشخص شود هر کتاب (به عنوان محصول) به کدام کاربر (به عنوان نویسنده) مرتبط است.
 بعد از اینکه مدل‌های مربوط به برنامه خود را ساختیم، اولا نیاز به ساخت کلاس کانتکست نداریم چون خود MVC5 کلاس کانتکست را دارد؛ ثانیا نیاز به ایجاد مدل برای جداول اعتبارسنجی نیست، چون کلاسی برای فیلدهای اضافی ما که علاقمندیم به جدول Users اضافه شود، از پیش تعیین گردیده است.

دو کلاسی که با فلش علامت گذاری شده اند، تنها فایل‌های موجود در پوشه مدل، بعد از ایجاد یک پروژه هستند. فایل IdentityModel را به عنوان فایل کانتکست خواهیم شناخت (چون یکی از کلاسهایش Context است). همانطور که پیش از این گفتیم با وجود این فایل نیازی به ایجاد یک کلاس مشتق شده از DbContext نیست. همانطور که در کد زیر میبینید این فایل دارای دو کلاس است:
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
      
}
کلاس اول همان کلاسی است که اگر به آن پراپرتی اضافه کنیم، بطور اتوماتیک آن پراپرتی به جدول ASPNetUsers در دیتابیس اضافه می‌شود و دیگر نگران فیلدهای نداشته‌ی جدول کاربران ASP.NET نخواهیم بود. مثلا در کد زیر چند عنوان به این جدول اضافه کرده ایم.
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        [Display(Name = "نام انگلیسی")]
        public string EnglishName { get; set; }

        [Display(Name = "نام سیستمی")]
        public string NameInSystem { get; set; }

        [Display(Name = "نام فارسی")]
        public string PersianName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        public string Email { get; set; }
     }
}
کلاس دوم نیز محل معرفی مدلها به منظور ایجاد در دیتابیس است. به ازای هر مدل یک جدول در دیتابیس خواهیم داشت. مثلا در شکل فوق سه پراپرتی به جدول کاربران اضافه میشود. دقت داشته باشید با اینکه هیچ مدلی برای جدول کاربران نساخته ایم اما کلاس ApplicatioUsers کلاسی است که به ما امکان دسترسی به مقادیر این جدول را می‌دهد(دسترسی به معنای اضافه و حذف وتغییر مقادیر این جدول است) (در MVC4 به کمک کلاس membership کارهای مشابهی انجام میدادیم)
 در ساختن مدل هایمان نیز اگر نیاز به ارتباط با جدول کاربران باشد، از همین کلاس فوق استفاده میکنیم. کلاس واسط(مدل واسط) بین AspNetUsers و Product در کد زیر زیر نشان داده شده است :
namespace MyShop.Models
{
    public class AuthorProduct
    {
        [Key]
        public int AuthorProductId { get; set; }
       /* public int UserId { get; set; }*/

        [Display(Name = "User")]
        public string ApplicationUserId { get; set; }

        public int ProductID { get; set; }

        public virtual Product Product { get; set; }
    
        public virtual ApplicationUser ApplicationUser { get; set; }
    }
}
همانطور که مشاهده میکنید، به راحتی ارتباط را برقرار کردیم و برای برقراری این ارتباط از کلاس ApplicationUser استفاده کردیم. پراپرتی ApplicationUserId نیز فیلد ارتباطی ما با جدول کاربران است. جدول product هم نکته خاصی ندارد و به شکل زیر مدل خواهد شد.
namespace MyShop.Models
{
    [DisplayName("محصول")]
    [DisplayPluralName("محصولات")]
    public class Product
    {
        [Key]
        public int ProductID { get; set; }

        [Display(Name = "گروه محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductGroupID { get; set; }

        [Display(Name = "مدت زمان")]
        public string Duration { get; set; }

   
        [Display(Name = "نام تهیه کننده")]
        public string Producer { get; set; }

        [Display(Name = "عنوان محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string ProductTitle { get; set; }

        [StringLength(200)]
        [Display(Name = "کلید واژه")]
        public string MetaKeyword { get; set; }

        [StringLength(200)]
        [Display(Name = "توضیح")]
        public string MetaDescription { get; set; }

        [Display(Name = "شرح محصول")]
        [UIHint("RichText")]
        [AllowHtml]
        public string ProductDescription { get; set; }

        [Display(Name = "قیمت محصول")]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#,0 ریال}")]
        [UIHint("Integer")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductPrice { get; set; }
        [Display(Name = "تاریخ ثبت محصول")]

        public DateTime? RegisterDate { get; set; }

    }
}
به این ترتیب هم ارتباطات را برقرار کرده‌ایم و هم از ساختن یک UserProfile اضافی خلاص شدیم.
برای پر کردن مقادیر اولیه نیز به راحتی از seed موجود در Configuration.cs مربوط به migration استفاده میکنیم. نمونه‌ی اینکار در کد زیر موجود است:
protected override void Seed(MyShop.Models.ApplicationDbContext context)
        {
            context.Users.AddOrUpdate(u => u.Id,
                      new ApplicationUser() {  Id = "1",EnglishName = "MortezaDalil", PersianName = "مرتضی دلیل", UserDescription = "توضیح در مورد مرتضی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "2", EnglishName = "MarhamatZeinali", PersianName = "محسن احمدی", UserDescription = "توضیح در مورد محسن", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "3", EnglishName = "MahdiMilani", PersianName = "مهدی محمدی", UserDescription = "توضیح در مورد مهدی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "4", EnglishName = "Babak", PersianName = "بابک", UserDescription = "کاربر معمولی بدون توضیح", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }
                     
                        );


            context.AuthorProducts.AddOrUpdate(u => u.AuthorProductId,
              new AuthorProduct() { AuthorProductId = 1, ProductID = 1, ApplicationUserId = "2" },
              new AuthorProduct() { AuthorProductId = 2, ProductID = 2, ApplicationUserId = "1" },
              new AuthorProduct() { AuthorProductId = 3, ProductID = 3, ApplicationUserId = "3" }

          );
 می‌توانیم از کلاس‌های خود Identity برای انجام روش فوق استفاده کنیم؛ فرض کنید بخواهیم یک کاربر به نام admin و با نقش admin به سیستم اضافه کنیم.
            if (!context.Users.Where(u => u.UserName == "Admin").Any())
            {
                var roleStore = new RoleStore<IdentityRole>(context);
                var rolemanager = new RoleManager<IdentityRole>(roleStore);

                var userstore = new UserStore<ApplicationUser>(context);
                var usermanager = new UserManager<ApplicationUser>(userstore);
                
                var user = new ApplicationUser {UserName = "Admin"};
                
                usermanager.Create(user, "121212");
                rolemanager.Create(new IdentityRole {Name = "admin"});
                
                usermanager.AddToRole(user.Id, "admin");
            }
   در عبارت شرطی موجود کد فوق، ابتدا چک کردیم که چنین یوزری در دیتابیس نباشد، سپس از کلاس RoleStore که پیاده سازی شده‌ی اینترفیس IRoleStore است استفاده کردیم. سازنده این کلاس به کانتکست نیاز دارد؛ پس به آن context را به عنوان ورودی می‌دهیم. در خط بعد، کلاس rolemanager را داریم که بخشی از پکیج Core است و پیش از این درباره اش توضیح دادیم ( یکی از دو رفرنسی که خوبخود به پروژه اضافه میشوند) و از ویژگی‌های Identity است. به آن آبجکتی که از RoleStore ساختیم را پاس میدهیم و خود کلاس میداند چه چیز را کجا ذخیره کند.
برای ایجاد کاربر نیز همین روند را انجام می‌دهیم. سپس یک آبجکت به نام user را از روی کلاس ApplicationUser میسازیم. برای آن پسورد 121212 سِت میکنیم و نقش ادمین را به آن نسبت میدهیم. این روش قابل تسری به تمامی بخش‌های برنامه شماست. میتوانید عملیات کنترل و مدیریت اکانت را نیز به همین شکل انجام دهید. ساخت کاربر و لاگین کردن یا مدیریت پسورد نیز به همین شکل قابل انجام است.
 بعد از آپدیت دیتابیس تغییرات را مشاهده خواهیم کرد. 
مطالب
پیاده سازی یک MediaTypeFormatter برای پشتیبانی از MultiPart/form-data در Web API

Media Type یا MIME Type نشان دهنده فرمت یک مجموعه داده است. در HTTP، مدیا تایپ بیان کننده فرمت message body یک درخواست / پاسخ است و به دریافت کننده اعلام می‌کند که چطور باید پیام را بخواند. محل استاندارد تعیین Mime Type در هدر Content-Type است. درخواست کننده می‌تواند با استفاده از هدر Accept لیستی از MimeType‌های قابل قبول را به عنوان پاسخ، به سرور اعلام کند.

Asp.net Web API  از MimeType برای تعیین نحوه serialize یا deserialize کردن محتوای دریافتی / ارسالی استفاده می‌کند


MediaTypeFormatter

Web API برای خواندن/درج پیام در بدنه درخواست/پاسخ از MediaTypeFormmater‌‌ها استفاده می‌کند. اینها کلاس‌هایی هستند که نحوه‌ی Serialize کردن و deserialize کردن اطلاعات به فرمت‌های خاص را تعیین می‌کنند. Web API به صورت توکار دارای formatter هایی برای نوع‌های XML ، JSON، BSON و Form-UrlEncoded می‌باشد. همه این‌ها کلاس پایه MediaTypeFormatter را پیاده سازی می‌کنند. 


مسئله

یک پروژه Web API بسازید و view model زیر را در آن تعریف کنید:

public class NewProduct
    {
        [Required]
        public string Name { get; set; }

        public double Price { get; set; }

        public byte[] Pic { get; set; }
    }

همانطور که می‌بینید یک فیلد از نوع byte[] برای تصویر محصول در نظر گرفته شده است.

حالا یک کنترلر API  ساخته و اکشنی برای دریافت اطلاعات محصول جدید از کاربر می‌نویسیم :
public class ProductsController : ApiController
    {
        [HttpPost]
        public HttpResponseMessage PostProduct(NewProduct model)
        {
            if (ModelState.IsValid)
            {
                //  ثبت محصول 

                return new HttpResponseMessage(HttpStatusCode.Created);
            }

            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }

    }

و یک صفحه html به نام index.html که حاوی یک فرم برای ارسال اطلاعات باشد :

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <h1>ساخت MediaTypeFormatter برای Multipart/form-data</h1>
    <h2>محصول جدید</h2>

    <form id="newProduct" method="post" action="/api/products" enctype="multipart/form-data">
        <div>
            <label for="name">نام محصول : </label>
            <input type="text" id="name" name="name" />
        </div>
        <div>
            <label for="price">قیمت : </label>
            <input type="number" id="price"  name="price" />
        </div>
        <div>
            <label for="pic">تصویر : </label>
            <input type="file" id="pic" name="pic" />
        </div>
        <div>
            <button type="submit">ثبت</button>
        </div>
    </form>
</body>
</html>

زمانی که فرم حاوی فایلی برای آپلود باشد مشخصه encType باید برابر با Multipart/form-data مقداردهی شود تا اطلاعات فایل به درستی کد شوند. در زمان ارسال فرم Content-type درخواست برابر با Multipart/form-data و فرمت اطلاعات درخواست ارسالی به شکل زیر خواهد بود :


همانطور که می‌بینید هر فیلد در فرم، در یک بخش جداگانه قرار گرفته است که با خط چین هایی از هم جدا شده اند. هر بخش، header‌های جداگانه خود را دارد.

- Content-Disposition که نام فیلد و نام فایل را شامل می‌شود .

- content-type که mime type مخصوص آن بخش از داده‌ها را مشخص می‌کند.

پس از اینکه فرم را تکمیل کرده و ارسال کنید ، با پیام خطای زیر مواجه می‌شوید :

خطای روی داده اعلام می‌کند که Web API فاقد MediaTypeFormatter برای خواندن اطلاعات ارسال شده با فرمتMultiPart/Form-data  است.  Web API برای خواندن و بایند کردن پارامترهای complex Type از درون بدنه پیام یک درخواست از MediaTypeFormatter استفاده می‌کند و همانطور که گفته شد Web API فاقد Formatter توکار برای deserialize کردن داده‌های با فرمت Multipart/form-data است.

راه حل‌ها :

روشی که در سایت asp.net برای آپلود فایل در web api استفاده شده، عدم استفاده از پارامترها و خواندن محتوای Request در درون کنترلر است. که به طبع در صورتی که بخواهیم کنترلرهای تمیز و کوچکی داشته باشیم روش مناسبی نیست. از طرفی امتیاز parameter binding و modelstate را هم از دست خواهیم داد.

روش دیگری که می‌خواهیم در اینجا پیاده سازی کنیم ساختن یک MediaTypeFormatter برای خواندن فرمت Multipart/form-data است. با این روش کد موردنیاز کپسوله شده و امکان استفاده از binding و modelstate را خواهیم داشت.

برای ساختن یک MediaTypeFormatter یکی از 2 کلاس MediaTypeFormatter یا  BufferedMediaTypeFormatter  را باید پیاده سازی کنیم . تفاوت این دو در این است که BufferedMediaTypeFormatter برخلاف MediaTypeFormatter از متدهای synchronous استفاده می‌کند.

 
پیاده سازی :

یک کلاس به نام MultiPartMediaTypeFormatter می‌سازیم و کلاس MediaTypeFormatter را به عنوان کلاس پایه آن قرار می‌دهیم .

public class MultiPartMediaTypeFormatter : MediaTypeFormatter
{
    ...
}

ابتدا در تابع سازنده کلاس فرمت هایی که می‌خواهیم توسط این کلاس خوانده شوند را تعریف می‌کنیم :

        public MultiPartMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
        }
در اینجا Multipart/form-data را به عنوان تنها نوع مجاز تعریف کرده ایم.

سپس با پیاده سازی توابع CanReadType و CanWriteType مربوط به کلاس MediaTypeFormatter مشخص می‌کنیم که چه مدل‌هایی را می‌توان توسط این کلاس  serialize / deserialize کرد. در اینجا چون می‌خواهیم این کلاس محدود به یک مدل خاص نباشد، از یک اینترفیس برای شناسایی کلاس‌های مجاز استفاده می‌کنیم .

    public interface INeedMultiPartMediaTypeFormatter
    {
    }

و آنرا به کلاس newProduct اضافه می‌کنیم :

public class NewProduct : INeedMultiPartMediaTypeFormatter
{
 ...
}
از آنجا که تنها نیاز به خواندن اطلاعات داریم و قصد نوشتن نداریم، در متد CanWriteType مقدار false را برمی گردانیم .
        public override bool CanReadType(Type type)
        {
            return typeof(INeedMultiPartMediaTypeFormatter).IsAssignableFrom(type);
        }

        public override bool CanWriteType(Type type)
        {
            return false;
        }

و اما تابع ReadFromStreamAsync که کار خواندن محتوای ارسال شده و بایند کردن آنها به پارامترها را برعهده دارد 

public async override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
که در آن پارامتر type مربوط به مدل مشخص شده به عنوان پارامتر اکشن (NewProduct)است و پارامتر content محتوای درخواست را در خود دارد.

ابتدا محتوای ارسال شده را خوانده و اطلاعات فرم را استخراج می‌کنیم و از طرف دیگر با استفاده از کلاس Activator یک نمونه از مدل جاری را ساخته و لیست property‌های آنرا استخراج می‌کنیم.

            MultipartMemoryStreamProvider provider = await content.ReadAsMultipartAsync();
            IEnumerable<HttpContent> formData = provider.Contents.AsEnumerable();

            var modelInstance = Activator.CreateInstance(type);
            IEnumerable<PropertyInfo> properties = type.GetProperties();

سپس در یک حلقه به ترتیب برای هر property متعلق به مدل، در میان اطلاعات فرم جستجو می‌کنیم. برای پیدا کردن اطلاعات متناظر با هر property در هدر Content-Disposition که در بالا توضیح داده شد، به دنبال فیلد همنام با property می‌گردیم .

            foreach (PropertyInfo prop in properties)
            {
                var propName = prop.Name.ToLower();
                var propType = prop.PropertyType;

                var data = formData.FirstOrDefault(d => d.Headers.ContentDisposition.Name.ToLower().Contains(propName));
در صورتی که فیلدی وجود داشته باشد کار را ادامه می‌دهیم .

 گفتیم که هر فیلد یک هدر، Content-Type هم می‌تواند داشته باشد. این هدر به صورت پیش فرض معادل text/plain است و برای فیلدهای عادی قرار داده نمی‌شود . در این مثال چون فقط یک فیلد غیر رشته ای داریم فرض را بر این گرفته ایم که در صورت وجود Content-Type، فیلد مربوط به تصویر است. در صورتیکهContentType  وجود داشته باشد، محتوای فیلد را به شکل Stream خوانده به byte[] تبدیل و با استفاده از متد SetValue در property مربوطه قرار می‌دهیم.

                if (data != null)
                {
                    if (data.Headers.ContentType != null)
                    {
                        using (var fileStream = await data.ReadAsStreamAsync())
                        {
                            using (MemoryStream ms = new MemoryStream())
                            {
                                fileStream.CopyTo(ms);
                                prop.SetValue(modelInstance, ms.ToArray());
                            }                            
                        }
                    }

در صورتی که Content-Type غایب باشد بدین معنی است که محتوای فیلد از نوع رشته است ( عدد ، تاریخ ، guid ، رشته ) و باید به نوع مناسب تبدیل شود. ابتدا آن را به صورت یک رشته می‌خوانیم و با استفاده از Convert.ChangeType آنرا به نوع مناسب تبدیل می‌کنیم و در property متناظر قرار می‌دهیم .

                if (data != null)
                {
                    if (data.Headers.ContentType != null)
                    {
                        //...
                    }
                    else
                    {
                        string rawVal = await data.ReadAsStringAsync();
                        object val = Convert.ChangeType(rawVal, propType);

                        prop.SetValue(modelInstance, val);
                    }
                }
و در نهایت نمونه ساخته شده از مدل را برگشت می‌دهیم.
return modelInstance;
برای فعال کردن این Formatter باید آنرا به لیست formmater‌های web api اضافه بکنیم. فایل WebApiConfig در App_Start را باز کرده و خط زیر را به آن اضافه می‌کنیم:
config.Formatters.Add(new MultiPartMediaTypeFormatter());
حال اگر مجددا فرم را به سرور ارسال کنیم، با پیام خطایی، مواجه نشده و عمل binding با موفقیت انجام می‌گیرد.

مطالب
چه زمانی بهتر است از بانک‌های اطلاعاتی NoSQL استفاده کرد و چه زمانی خیر؟
در سناریوهای خاصی، بانک‌های اطلاعاتی NoSQL خوش می‌درخشند و در بسیاری از موارد دیگر، بانک‌های اطلاعاتی رابطه‌ای بهترین گزینه انتخابی می‌باشند و نه بانک‌های اطلاعاتی NoSQL. در ادامه به بررسی این موارد خواهیم پرداخت.


در چه برنامه‌هایی استفاده از بانک‌های اطلاعاتی NoSQL مناسب‌تر است؟
1) برنامه‌های مدیریت محتوا
2) کاتالوگ‌های محصولات (هر برنامه‌ای با تعدادی شیء و خصوصا متادیتای متغیر)
3) شبکه‌های اجتماعی
4) Big Data
5) سایر


1) برنامه‌های مدیریت محتوا
بانک‌های اطلاعاتی NoSQL سندگرا، جهت تهیه برنامه‌های مدیریت محتوا، بسیار مناسب هستند. در این نوع برنامه‌ها، یک سری محتوا که دارای متادیتایی هستند، ذخیره خواهند شد. این متادیتاها مانند نوع، گروه و هر نوع خاصیت دیگری، می‌تواند باشند. برای ذخیره سازی این نوع اطلاعات، جفت‌های key-value بسیار خوب عمل می‌کنند. همچنین در بانک‌های اطلاعاتی سندگرای NoSQL، با استفاده از مفهوم برچسب‌ها، امکان الصاق فایل‌های متناظری به اسناد پیش بینی شده‌است. همانطور که در قسمت قبل نیز ذکر شد، در Document stores، نگارش‌های قدیمی اسناد نیز حفظ می‌شوند. به این ترتیب، این خاصیت و توانمندی توکار، امکان دسترسی به نگارش‌های مختلف یک محتوای خاص را به سادگی میسر می‌سازد. به علاوه اکثر Document stores امکان دسترسی به این مستندات را به کمک URLها و REST API، به صورت خودکار فراهم می‌سازند.
برای نمونه به CouchDB، عنوان Web database نیز داده شده است؛ از این جهت که یک برنامه وب را می‌توان داخل بانک اطلاعاتی آن قرار داد. در اینجا منظور از برنامه وب، یک وب سایت قابل دسترسی از طریق URLها است و نه برنامه‌های سازمانی وب. برای نمونه ساختاری شبیه به برنامه معروف EverNote را می‌توان داخل این نوع بانک‌های اطلاعاتی به سادگی ایجاد کرد (خود بانک اطلاعاتی تشکیل شده است از یک وب سرور که REST API را پشتیبانی کرده و امکان دسترسی به اسناد را بدون نیاز به کدنویسی اضافه‌تری، از طریق URLها و HTTP Verbs استاندارد مهیا می‌کند).


2) کاتالوگ‌های محصولات
محصولات در یک کاتالوگ، ویژگی‌های مشابه یکسان فراوانی دارند؛ اما تعدادی از این محصولات، دارای ویژگی‌هایی خاص و منحصربفردی نیز می‌باشند.
مثلا یک شیء محصول را درنظر بگیرید که دارای خواص مشترک و یکسان شماره، نام، توضیحات و قیمت است. اما بعضی از محصولات، بسته به رده‌ی خاصی که دارند، دارای ویژگی‌های خاصی مانند قدرت تفکیک، رنگ، سرعت و غیره نیز هستند که از هر گروه، به گروه دیگری متغیر است.
برای مدیریت یک چنین نیازی، هر دو گروه key-value stores و wide column stores بانک‌های اطلاعاتی NoSQL مناسب هستند؛ از این جهت که در یک key-value store نیازی به تعریف هیچ نوع ساختار خاصی، در ابتدای کار نیست و این ساختار می‌تواند از هر رکورد، به رکورد دیگری متفاوت باشد.
یا برای نمونه، یک برنامه فرم ساز را درنظر بگیرید که هر فرم آن، هر چند دارای یک سری خواص ثابت مانند نام، گروه و امثال آن است، اما هر کدام دارای فیلدهای تشکیل دهنده متفاوتی نیز می‌باشد. به این ترتیب با استفاده از key-value stores، دیگری نیازی به نگران بودن در مورد نحوه مدیریت اسکیمای متغیر مورد نیاز، نخواهد بود.


3) شبکه‌های اجتماعی
همانطور که در قسمت قبل نیز بحث شد، نوع خاص Graph databases برای کاربردهای برنامه‌های شبکه‌های اجتماعی و ردیابی تغییرات آن‌ها بسیار مفید و کارآ هستند. برای مثال در یک شبکه افراد دارای تعدادی دنبال کننده هستند؛ عضو گروه‌های مختلف می‌باشند، در قسمت‌های مختلفی نظر و مطلب ارسال می‌کنند. در اینجا، اشیاء نسبت به یکدیگر روابط مختلفی دارند. با استفاده از Graph databases، تشکیل روابط self-joins و تو در تو و بسیاری از روش‌های خاص، مانند روابط many-to-many که در بانک‌های اطلاعاتی رابطه‌ای با تمهیدات ویژه‌ای قابل تشکیل هستند، با سهولت بهتری مدیریت خواهند شد.


4) Big Data
الگوریتم MapReduce، برای کار با حجم داده‌های عظیم، طراحی شده است و در این بین، بانک‌های اطلاعاتی Wide column store (که در قسمت قبل بررسی شدند) و یا حتی Key-value store (مانند Amazon DynamoDB) بیشتر کاربرد دارند. در سناریوهای داده‌های عظیم، واژه‌های Hadoop و Hbase دنیای NoSQL را زیاد خواهید شنید. Hadoop نسخه سورس باز MapReduce گوگل است و Hbase نیز نسخه سورس باز BigTable گوگل می‌باشد. مفاهیم پایه‌ای Sharding و فایل سیستم‌های Append-only (با سرعت بالای نوشتن) نیز به مدیریت BigData کمک می‌کنند.
در اینجا بحث مهم، خواندن اطلاعات و آنالیز آن‌ها است و نه تهیه برنامه‌های معروف CRUD. بسیاری از اعمال آماری و ریاضی مورد نیاز بر روی داده‌های عظیم، نیازی به اسکیمای از پیش مشخص شده بانک‌های اطلاعاتی رابطه‌ای را ندارند و یا در اینجا قابلیت‌های نوشتن کوئری‌های پیچیده نیز آنچنان مهم نیستند.


5) سایر کاربردها
- هر سیستمی که اطلاعات Log مانند را تولید می‌کند. منظور از Log، اطلاعاتی است که در حین رخداد خاصی تولید می‌شوند. عموما مرسوم است که این نوع اطلاعات را در فایل‌ها، بجای بانک اطلاعاتی ذخیره کرد. بنابراین مدیریت این نوع فایل‌ها توسط بانک‌های اطلاعاتی NoSQL، قابلیت انجام امور آماری را بر روی آن‌ها ساده‌تر خواهد ساخت.
- مدیریت اطلاعات برنامه‌هایی مانند سیستم‌های EMail.



و در چه برنامه‌هایی استفاده از بانک‌های اطلاعاتی رابطه‌ای مناسب‌تر است؟

اگر تا اینجا به مزایای استفاده از بانک‌های اطلاعاتی NoSQL اشاره شد، بدین معنا نیست که بانک‌های اطلاعاتی رابطه‌ای، منسوخ شده‌اند یا دیگر قدر و قیمتی ندارند. واقعیت این است که هنوز بازه وسیعی از کاربردها را می‌توان به کمک بانک‌های اطلاعاتی رابطه‌ای بهتر از بانک‌های اطلاعاتی NoSQL مدیریت کرد. این کاربردها و مزیت‌ها در 5 گروه عمده خلاصه می‌شوند:
1) نیاز به تراکنش‌ها
2) اسکیمای پیش فرض
3) برنامه‌های LOB یا Line of business applications
4) زبان‌های کوئری نویسی پیشرفته
5) نیاز به امکانات گزارشگیری پیشرفته


1) نیاز به تراکنش‌ها
در سیستم‌های تجاری عمومی، نیاز به پیاده سازی مفهوم ACID که در قسمت‌های قبل به آن پرداخته شد، مانند Atomic transactions وجود دارد. Atomic transaction به زبان ساده به این معنا است که سیستم قادر است چندین دستور را در قالب یک گروه و در طی یک مرحله، به بانک اطلاعاتی اعمال کند و اگر یکی از این دستورات گروه در حال اعمال، با شکست مواجه شد، باید کل تراکنش برگشت خورده و امنیت کار تضمین گردد. در غیراینصورت با یک سیستم غیر هماهنگ مواجه خواهیم شد.
و همانطور که پیشتر نیز عنوان شد، سیستم‌های NoSQL، مبنای کار را بر اساس «عاقبت یک دست شدن» اطلاعات قرار داده‌اند؛ تا دسترسی پذیری به آن‌ها افزایش یافته و سرعت عملیات به این نحو بهبود یابد. در این نوع سیستم‌ها تضمینی در مورد ACID وجود ندارد.


2) اسکیمای پیش فرض
پروسه‌های متداول، دارای ساختاری مشخص و معمولی هستند. زیرا طراحی اولیه یک پروسه، بر مبنای مجموعه‌ای از اطلاعات است که همیشه باید وجود داشته باشند و اگر همانند بحث کاتالوگ‌های محصولات، نیاز به متادیتای متغیر نباشد، ساختار و اسکیمای یک پروسه، از ابتدای طراحی آن مشخص می‌باشد.
و ... تمام این‌ها را به خوبی می‌توان توسط بانک‌های اطلاعاتی رابطه‌ای، با تعریف یک اسکیمای مشخص، مدیریت کرد.


3) برنامه‌های LOB یا Line of business applications
در برنامه‌های تجاری متداول، طراحی طرحبندی فرم‌های برنامه یا انقیاد داده‌ها، بر اساس یک اسکیما و ساختار مشخص صورت می‌گیرد. بدون داشتن یک اسکیمای مشخص، امکان تعاریف انقیاد داده‌ها به صورت strongly typed وجود نخواهد داشت. همچنین کل مفهوم Object relational mapping و ORMهای مختلف نیز بر اساس وجود یک اسکیمای مشخص و از پیش تعیین شده کار می‌کند. بنابراین بانک‌های اطلاعاتی رابطه‌ای، انتخاب بسیار مناسبی برای تهیه برنامه‌های تجاری روزمره هستند.


4) زبان‌های کوئری نویسی پیشرفته
همانطور که عنوان شد برای تهیه کوئری بر روی اغلب بانک‌های اطلاعاتی NoSQL، باید توسط یک برنامه ثانویه، کار پیاده سازی الگوریتم Map Reduce را انجام داد. هر چند تعدادی از این نوع بانک‌های اطلاعاتی به صورت توکار دارای موتور MapReduce هستند، اما بسیاری از آن‌ها خیر. به همین جهت برای تهیه کوئری‌های متداول، کار پیاده سازی این برنامه‌های ثانویه مشکل خواهد بود. به این ترتیب نوشتن Ad Hoc queries و گزارشگیری بسیار مشکل می‌شوند.
علاوه بر امکانات خوب کوئری گرفتن در بانک‌های اطلاعاتی رابطه‌ای، این کوئری‌ها در زمان اجرا نیز بر اساس اسکیمای موجود، بسیار بهینه و با سرعت بالا اجرا می‌شوند. قابلیتی که رسیدن به آن در بانک‌های اطلاعاتی با اسکیمای متغیر، کار ساده‌ای نیست و باید آن‌را با کدنویسی شخصی بهینه کرد. البته اگر تعداد این نوع برنامه‌های ثانویه که به آن‌ها imperative query در مقابل declarative query بانک‌های رابطه‌ای می‌گویند، کم باشد، شاید یکبار نوشتن و بارها استفاده کردن از آن‌ها اهمیتی نداشته باشد؛ در غیراینصورت تبدیل به یک عذاب خواهد شد.


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

بنابراین به صورت خلاصه، بانک‌های اطلاعاتی رابطه‌ای، جهت مدیریت کارهای روزمره تجاری اغلب شرکت‌ها، بسیار ضروری و جزو مسایل پایه‌ای به‌شمار می‌روند و به این زودی‌ها هم قرار نیست با نمونه‌ی دیگری جایگزین شوند.
 
مطالب
SignalR
چند وقتی هست که در کنار بدنه اصلی دات‌نت فریم‌ورک چندین کتابخونه به صورت متن‌باز در حال توسعه هستند. این مورد در ASP.NET بیشتر فعاله و مثلا دو کتابخونه SignalR و WebApi توسط خود مایکروسافت توسعه داده میشه.
SignalR همونطور که در سایت بسیار خلاصه و مفید یک صفحه‌ای! خودش توضیح داده شده (^) یک کتابخونه برای توسعه برنامه‌های وب «زمان واقعی»! (real-time web) است:
Async library for .NET to help build real-time, multi-user interactive web applications.
برنامه‌های زمان واقعی به صورت خلاصه و ساده به‌صورت زیر تعریف میشن (^):
The real-time web is a set of technologies and practices that enable users to receive information as soon as it is published by its authors, rather than requiring that they or their software check a source periodically for updates.
یعنی کاربر سیستم ما بدون نیاز به ارسال درخواستی صریح! برای دریافت آخرین اطلاعات به روز شده در سرور، در برنامه کلاینتش از این تغییرات آگاه بشه. مثلا برنامه‌هایی که برای نمایش نمودارهای آماری داده‌ها استفاده میشه (بورس، قیمت ارز و طلا و ...) و یا مهمترین مثالش میتونه برنامه «چت» باشه. متاسفانه پروتوکل HTTP مورد استفاده در وب محدودیت‌هایی برای پیاده‌سازی این گونه برنامه‌ها داره. روش‌های گوناگونی برای پیاده‌سازی برنامه‌های زمان واقعی در وب وجود داره که کتابخونه SignalR فعلا از موارد زیر استفاده میکنه:
  1. تکنولوژی جدید WebSocket (^) که خوشبختانه پشتیبانی کاملی از اون در دات نت 4.5 (چهار نقطه پنج! نه چهار و نیم!) وجود داره. اما تمام مرورگرها و تمام وب سرورها از این تکنولوژی پشتیبانی نمیکنند و تنها برخی نسخه‌های جدید قابلیت استفاده از آخرین ورژن WebSocket رو دارند که میشه به کروم 16 به بالا و فایرفاکس 11 به بالا و اینترنت اکسپلورر 10 اشاره کرد (برای استفاده از این تکنولوژی در ویندوز نیاز به IIS 8.0 است که متاسفانه فقط در ویندوز 8.0 موجوده):
    Chrome 16, Firefox 11 and Internet Explorer 10 are currently the only browsers supporting the latest specification (RFC 6455).
  2.  یه روش دیگه Server-sent Events نام داره که داده‌های جدید رو به فرم رویدادهای DOM به سمت کلاینت میفرسته(^).
  3. روش دیگه‎‌ای که موجوده به Forever Frame معروفه که در این روش یک iframe مخفی درون کد html مسئول تبادل داده‌هاست. این iframe مخفی به‌صورت یک بلاک Chunked (^) به سمت کلاینت فرستاده میشه. این iframe که مسئول رندر داده‌های جدید در سمت کلاینت هست ارتباط خودش رو با سرور تا ابد! (برای همین بهش forever میگن) حفظ میکنه. هر وقت رویدادی سمت سرور رخ میده با استفاده از این روش داده‌ها به‌صورت تگ‌های script به این فریم مخفی فرستاده می‌شوند و چون مرورگرها محتوای html رو به صورت افزایشی (incrementally) رندر میکنن بنابراین این اسکریپتها به‌ترتیب زمان دریافت اجرا می‌شوند. (البته ظاهرا عبارت forever frame در صنعت عکاسی! معروف‌تره بنابراین در جستجو در زمینه این روش ممکنه کمی مشکل داشته باشین) (^).
  4. روش آخر که در کتابخونه SignalR ازش استفاده میشه long-polling نام داره. در روش polling معمولی پس از ارسال درخواست توسط کلاینت، سرور بلافاصله نتیجه حاصله رو به سمت کلاینت میفرسته و ارتباط قطع میشه. بنابراین برای داده‌های جدید درخواست جدیدی باید به سمت سرور فرستاده بشه که تکرار این روش باعث افزایش شدید بار بر روی سرور و کاهش کارآمدی اون می‌شه. اما در روش long-polling پس از برقراری ارتباط کلاینت با سرور این ارتباط تا مدت زمان معینی (که توسط یه مقدار تایم اوت مشخص میشه و مقدار پیش‌فرضش 2 دقیقه است) برقرار میمونه. بنابراین کلاینت میتونه بدون ایجاد مشکلی در کارایی، داده‌های جدید رو از سرور دریافت کنه. به این روش در برنامه‌نویسی وب اصطلاحا برنامه‌نویسی کامت (Comet Programming) میگن (^ ^).
(البته روش‌های دیگری هم برای پیاده‌سازی برنامه‌های زمان اجرا وجود داره مثل کتابخونه node.js که جستجوی بیشتر به خوانندگان واگذار میشه)
SignalR برای برقراری ارتباط ابتدا بررسی میکنه که آیا هر دو سمت سرور و کلاینت قابلیت پشتیبانی از WebSocket رو دارند. در غیراینصورت سراغ روش Server-sent Events میره. اگر باز هم موفق نشد سعی به برقراری ارتباط با روش forever frame میکنه و اگر باز هم موفق نشد در آخر سراغ long-polling میره.
با استفاده از SignalR شما میتونین از سرور، متدهایی رو در سمت کلاینت فراخونی کنین. یعنی درواقع با استفاده از کدهای سی شارپ میشه متدهای جاوااسکریپت سمت کلاینت رو صدا زد!
بطور خلاصه در این کتابخونه دو کلاس پایه وجود داره:
  1. کلاس سطح پایین PersistentConnection
  2. کلاس سطح بالای Hub
علت این نامگذاری به این دلیله که کلاس سطح پایین پیاده‌سازی پیچیده‌تر و تنظیمات بیشتری نیاز داره اما امکانات بیشتری هم در اختیار برنامه‌نویس قرار می‌ده.
خوب پس از این مقدمه نسبتا طولانی برای دیدن یک مثال ساده میتونین با استفاده از نوگت (Nuget) مثال زیر رو نصب و اجرا کنین (اگه تا حالا از نوگت استفاده نکردین قویا پیشنهاد میکنم که کار رو با دریافتش از اینجا آغاز کنین) :
PM> Install-Package SignalR.Sample
پس از کامل شدن نصب این مثال اون رو اجرا کنین. این یک مثال فرضی ساده از برنامه نمایش ارزش آنلاین سهام برخی شرکتهاست. میتونین این برنامه رو همزمان در چند مرورگر اجرا کنین و نتیجه رو مشاهده کنین.
حالا میریم سراغ یک مثال ساده. میخوایم یک برنامه چت ساده بنویسیم. ابتدا یک برنامه وب اپلیکیشن خالی رو ایجاد کرده و با استفاده از دستور زیر در خط فرمان نوگت، کتابخونه SignalR رو نصب کنین:
PM> Install-Package SignalR
پس از کامل شدن نصب این کتابخونه، ریفرنس‌های زیر به برنامه اضافه میشن:
Microsoft.Web.Infrastructure
Newtonsoft.Json
SignalR
SignalR.Hosting.AspNet
SignalR.Hosting.Common
برای کسب اطلاعات مختصر و مفید از تمام اجزای این کتابخونه به اینجا مراجعه کنین.
همچنین اسکریپت‌های زیر به پوشه Scripts اضافه میشن (این نسخه‌ها مربوط به زمان نگارش این مطلب است):
jquery-1.6.4.js
jquery.signalR-0.5.1.js
بعد یک کلاس با نام SimpleChat به برنامه اضافه و محتوای زیر رو در اون وارد کنین:
using SignalR.Hubs;
namespace SimpleChatWithSignalR
{
  public class SimpleChat : Hub
  {
    public void SendMessage(string message)
    {
      Clients.reciveMessage(message);
    }
  }
} 
دقت کنین که این کلاس از کلاس Hub مشتق شده و همچنین خاصیت Clients از نوع dynamic است. (در مورد جزئیات این کتابخونه در قسمت‌های بعدی توضیحات مفصل‌تری داده میشه)
سپس یک فرم به برنامه اضافه کرده و محتوای زیر رو در اون اضافه کنین:
<input type="text" id="msg" />
<input type="button" value="Send" id="send" /><br />
<textarea id='messages' readonly="true" style="height: 200px; width: 200px;"></textarea>
<script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.signalR-0.5.1.min.js" type="text/javascript"></script>
<script src="signalr/hubs" type="text/javascript"></script>
<script type="text/javascript">
  var chat = $.connection.simpleChat;
  chat.reciveMessage = function (msg) {
   $('#messages').val($('#messages').val() + "-" + msg + "\r\n"); 
  };
  $.connection.hub.start();
  $('#send').click(function () {
    chat.sendMessage($('#msg').val());
  });
</script>
همونطور که میبینین برنامه چت ما آماده شد! حالا برنامه رو اجرا کنین و با استفاده از دو مرورگر مختلف نتیجه رو مشاهده کنین.
نکته کلیدی کار SignalR در خط زیر نهفته است:
<script src="signalr/hubs" type="text/javascript"></script>
اگر محتوای آدرس فوق رو دریافت کنین می‌بینین که موتور این کتابخانه تمامی متدهای موردنیاز در سمت کلاینت رو با استفاده از کدهای جاوااسکریپت تولید کرده. البته در این کد تولیدی از نامگذاری camel Casing استفاده میشه، بنابراین متد SendMessage در سمت سرور به‌صورت sendMessage در سمت کلاینت در دسترسه.
امیدوارم تا اینجا تونسته باشم علاقه شما به استفاده از این کتابخونه رو جلب کرده باشم. در قسمت‌های بعد موارد پیشرفته‌تر این کتابخونه معرفی میشه.
اگه علاقه‌مند باشین میتونین از این ویکی اطلاعات بیشتری بدست بیارین.


به روز رسانی
در دوره‌ای به نام SignalR در سایت، به روز شده‌ای این مباحث را می‌توانید مطالعه کنید.
مطالب
نکاتی توصیه ای برای برنامه نویسی اندروید : قسمت اول
اگر جدیدا قصد برنامه نویسی اندروید را کرده‌اید، یا هنوز روش‌های متدوالی را برای کار با این زبان انتخاب نکرده‌اید؛ به نظرم این مقاله می‌تواند کمک خوبی برای شما باشد. مسائلی که بیان میکنم در واقع از تجربیات شخصی و راه حل هایی است که برای خودم تعیین کرده‌ام و تعدادی از آن‌ها را در طول مدتی که در این زمینه فعالیت کرده‌ام، از جاهای مختلف دیده و در یک جا گردآوری کرده‌ام. برای نامگذاری اشیاء و متغیرها و دیگر موارد، من از این قاعده پیروی میکنم که به نظرم بسیار ایده آل می‌باشد. الگوی معماری هم که جدیدا مورد استفاده قرار داده‌ام، الگوی MVP است که نمونه‌ای از آن، در گیت هاب قرار گرفته است. البته این مثال ساده تر نیز وجود دارد. تشریح کامل این معماری را به همراه آزمون واحد آن، می‌توانید در این مقاله سه قسمتی ببینید.

در اینجا، یک سری نکات را در طول برنامه نویسی، متذکر می‌شوم تا مدیریت کدهای شما را در اندروید راحت‌تر کند.

یک نکته‌ی دیگر را که باید متذکر شوم این است که همه اصطلاحاتی که در این مقاله استفاده می‌شوند بر اساس اندروید استادیو و مستندات رسمی گوگل است است؛ به عنوان نمونه عبارت‌های ماژول و پروژه آن چیزی هستند که ما در اندروید استادیو به آن‌ها اشاره می‌کنیم، نه آنچه که کاربران Eclipse به آن اشاره می‌کنند.

یک. برای هر تکه کد و یا متدی که می‌نویسید مستندات کافی قرار دهید و اگر این متد نیاز به مجوز خاصی دارد مانند نمونه زیر، آن را حتما ذکر کنید:
/**
     *
     * <p>
     *   check network is available or not <br/>
     *   internet connection is not matter,for check internet connection refer to IsInternetConnected() Method in this class
     * </p>
       * <p>
     *   Required Permission : <b>android.permission.ACCESS_NETWORK_STATE</b>
     * </p>
     * @param context
     * @return returns true if a network is available
     */
    public boolean isNetworkAvailable(Context context) {

        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }
همچنین اگر، مورد خاص دیگری مثل بالا بود، حتما آن را ذکر کنید. می‌توانید از تگ گذاری در کامنت ها نیز استفاده کنید. از ویژگی‌های کامنت todo در اندروید استادیو این است که میتوانید در حین کار با سیستم گیت نیز از آن بهره ببرید  و قبل از کامیت کردن کد، کدهای todo به شما یادآوری شوند و هر پیکربندی را که لازم دارید، روی آن انجام دهید.



دو. از یک کلاس واحد جهت استفاده از اطلاعات عمومی و یا ثابت‌ها استفاده نمایید. این اطلاعات می‌توانند شامل: مسیرها، آدرس‌های وب سرویس، شماره اختصاصی هر نوتیفیکیشن و .... باشند. برای اینکار میتوان هر کدام از اطلاعات را داخل یک کلاس قرار داد و همه این کلاس‌ها را به صورت استاتیک تعریف کنید تا بدین شکل در دسترس قرار بگیرند (از الگوی singleton هم می‌توان استفاده کرد).
public class ProjectSettings
{
       public static NotificationsId=new NotificationsId();
       public static UrlAddresss=new UrlAddresss();
       public static SdPath=new SdPath();
       ......
}
نحوه صدا زدن هم به همین شکل می‌شود:
ProjectSettings.NotificationsId.UpdateNotificationId
بدین شکل هم به طور ساده و مفهومی صدا زده می‌شود و هم اینکه در همه جای برنامه این ثابت‌ها و مقادیر قابل استفاده هستند. به عنوان مثال به شماره هر نوتیفیکیشن از همه جا دسترسی دارید و هم اینکه شماره‌ای تکراری اشتباها انتخاب نمی‌شود.

سه. حداکثر استفاده از اینترفیس را به خصوص برای UI انجام بدهید:
به عنوان نمونه، بسیاری نمایش یک toast را به شکل زیر انجام می‌دهند:
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
یا اینکه برای یک دیالوگ مستقیما و در جا همانجا به کدنویسی مشغول می‌شوند. این روش‌ها هیچ مشکلی ندارند ولی در آینده نگهداری کد را مشکل می‌کنند. مثلا تصور کنید شما بسیاری از جاهای برنامه، Toast زدید و حالا قصد دارید در نسخه بعدی برنامه، toast‌های دلخواه و یا custom ایی را ایجاد کنید. در این صورت مجبورید کل برنامه را رصد کرده و هر جا toast هست آن را تغییر دهید. در اینجا هم اصول DRY را نادیده گرفته‌اید و هم زحمت شما زیاد شده‌است و حتی ممکن است یک یا چندتایی از قلم بیفتند. برای دیالوگ‌ها هم بدین صورت خواهد بود و خیلی از مسائل دیگر. به همین جهت استفاده از اینترفیس‌ها توصیه می‌شود و فردا نیز اگر باز یک کلاس دیگر را نوشتید، خیلی راحت آن را با کلاس فعلی تعویض می‌کنید.
public interface IMessageUI
{
    void ShowToast(Context context,String message);
}

public class MessageUI impelement IMessageUI
{
      public void ShowToast(Context context,string message)
       {
              Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
       }
}

چهار
. اگر برای اولین بار است وارد اندروید می‌شوید، خوب چرخه‌های یک شیء، چون اکتیویتی یا فراگمنت را یاد بگیرید تا در آینده با مشکلات خاصی روبرو نشوید.
به عنوان مثال درست است که اولین رویداد فراخوانی در onCreate رخ میدهد ولی همیشه محل مناسبی برای دریافت دیتاها در زمان اولیه نیست. به عنوان مثال تصور کنید که لیستی در اکتیویتی A دارید و به اکتیویتی B می‌روید و یک آیتم به اطلاعات اضافه می‌شود و موقعی که به اکتیویتی A بر می‌گردید، زیاد تعجب نکنید که لیست دقیقا به همان شکل قبلی است و خبری از آیتم جدید نیست.
  چون اکتیویتی در حالت stop بوده و بعد از آن به حالت Resume رفته و تا موقعی که این اکتیویتی از حافظه خارج نشود یا گوشی چرخش نداشته باشد، واکشی دیتاها صورت نخواهد گرفت. پس بهترین مکان در این حالت، رویداد OnStart است که در هر دو وضعیت صدا زده می‌شود؛ یا اینکه در OnRestatr روی آداپتور تغییرات جدید را اعمال کنید تا نیازی به واکشی مجدد داده‌ها نباشد. 



به طور خلاصه نحوه اجرای رویدادها بدین شکل است که ابتدای رویداد OnCreate اجرا می‌شود که هنوز هیچ UI ئی در آن پیاده سازی نشده‌است و شما در اینجا موظفید Layout خود را معرفی کنید. رویداد OnStart بعد از آن موقعی که UI آماده شده است، اجرا می‌گردد. سپس رویداد OnResume اجرا می‌شود.

 تا بدینجا اکتیویتی مشکلی ندارد و میتواند به عملیات پاسخ دهد ولی اگر قسمتی از اکتیویتی در زیر لایه‌ای از UI پنهان شود، به عنوان مثال دیالوگی باز شود که قسمتی از اکتیویتی را بپوشاند و یا منویی همانند تلگرام قسمتی از صفحه را بپوشاند، اکتیویتی اصطلاحا در حالت Pause قرار گرفته و بدین ترتیب رویداد OnPause اجرا می‌گردد. اگر همین دیالوگ بسته شود و مجددا اکتیویتی به طور کامل نمایان گردد مجددا رویداد OnResume اجرا می‌گردد.

از رویداد Onresume میتوانید برای کارهایی که بین زمان آغاز اکتیویتی و برگشت اکتیویتی مشترکند استفاده کرد. اگر به هر نحوی اکتیویتی به طور کامل پنهان شود٬، به این معناست که شما به اکتیویتی دیگری رفته‌اید رویداد OnStop اجرا شده‌است و در صورت بازگشت، رویداد OnRestart اجرا خواهد شد. ولی اگر مدت طولانی از رویداد OnStop بگذرد احتمال اینکه سیستم مدیریت منابع اندروید، اکتیویتی شما را از حافظه خارج کند زیاد است و رویداد OnDestroy صورت خواهد گرفت. در این حالت دفعه بعد، مجددا همه عملیات از ابتدا آغاز می‌گردند.

پنج. سرویس را با تردهای UI ترکیب نکنید. بعضا دیده می‌شود که کاربران AsyncTask را داخل سرویس استفاده می‌کنند ولی این را بدانید که سرویس یک ترد پردازشی جداگانه است و تضمینی برای ارتباط با UI به شما نمی‌دهند. هر چند گوگل جدیدا تمهیداتی را برای آن اندیشیده است که به شما اجازه اینکار را نمی‌دهد. ولی اگر باز هم اندروید استادیو به شما خورده‌ای نگرفت، خودتان این قانون را اجرا کنید. قرار نیست یک AsyncTask با سرویس ترکیب شود.

شش. اگر برنامه شما قرار است در چندین حالت مختلفی که اتفاق می‌افتد، یک کار خاصی را انجام دهد، برای برنامه‌تان یک Receiver بنویسید و در آن کدهای تکراری را نوشته و در محل‌های مختلف وقوع آن رویدادها، رسیور را صدا بزنید. برای نمونه برنامه تلگرام یک سرویس پیام رسان پشت صحنه دارد که در دو رویداد قرار است اجرا شوند. یکی موقعی که گوشی بوت خود را تکمیل کرده است و در حال آغاز فرایندهای سیستم عامل است و دیگر زمانی است که برنامه اجرا می‌شود. در اینجا تلگرام از یک رسیور سیستمی برای آگاهی از بوت شدن و یک رسیور داخل برنامه جهت آگاهی از اجرای برنامه استفاده میکند و هر دو به یک کلاس از جنس BroadcastReceiver متصلند:
      <receiver android:name=".AppStartReceiver" android:enabled="true">
            <intent-filter>
                <action android:name="org.telegram.start" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

public class AppStartReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        AndroidUtilities.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                ApplicationLoader.startPushService();
            }
        });
    }
}
برای نام رسیورهای داخلی هم میتوانید مورد شماره 2 را اجرا کنید.
برنامه تلگرام حتی برای حالت‌های پخش هم رسیورها استفاده کرده است که در همین رسیور وضعیت تغییر پلیر مشخص می‌شود:
    <receiver android:name=".MusicPlayerReceiver" >
            <intent-filter>
                <action android:name="org.telegram.android.musicplayer.close" />
                <action android:name="org.telegram.android.musicplayer.pause" />
                <action android:name="org.telegram.android.musicplayer.next" />
                <action android:name="org.telegram.android.musicplayer.play" />
                <action android:name="org.telegram.android.musicplayer.previous" />
                <action android:name="android.intent.action.MEDIA_BUTTON" />
                <action android:name="android.media.AUDIO_BECOMING_NOISY" />
            </intent-filter>
        </receiver>
اینگونه تلگرام میتواند از همه جا سرویس را کنترل کند. مثلا موقعی که دانلود یک موزیک تمام شده، سریعا پخش آن موزیک دانلود شده را آغاز کند.

هفت. اگر از یک ORM برای لایه داده‌ها استفاده می‌کنید (قبلا در سایت جاری در مورد ORM‌های اندروید صحبت کرده‌ایم و  ORM‌های خوش دستی که خودم از آن‌ها استفاده میکنم ActiveAndroid و CPORM هستند که هم کار کردن با آنها راحت است و هم اینکه امکانات خوبی را عرضه می‌کنند) در این نوع ORM‌ها شما نباید انتظار چیزی مانند EF را داشته باشید و در بعضی موارد باید کمی خودتان کمک کنید. به عنوان مثال در Active Android برای ایجاد یک inner join باید به شکل زیر بنویسید:
     From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on("poems.id=bank_poems_groups.poem")
               .where("BankGroup=?", String.valueOf(groupId));
        return query.execute();
همانطور که می‌بینید بخش‌هایی از آن مثل جوین‌ها و شرط‌ها را باید خودتان تکمیل کنید. از آنجا که ممکن است در آینده نام فیلد تغییر کند یا اینکه در حین انبوهی از کدها، عبارت رشته‌ای را اشتباه وارد کنید، بهتر است به این فرم کار کنید:
@Table(name="poems")
public class Poem extends Model {

    public static String tableName="poems";
    public static  String codeColumn="code";
    public static  String titleColumn="title";
    public static  String bookColumn="book";
    ......

    @Column(name="code",index = true)
    public int Code;

    @Column(name="title")
    public String Title;

    @Column(name="book")
    public Book Book;
.....}
در مدل بالا، نام فیلدها و جداول به صورت استاتیک تعریف شده‌اند. حالا می‌توانیم از این اسامی به راحتی در لایه سرویس استفاده کنیم:
    From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on(Poem.TableName+"."+ Poem.IdColumn+"="+ BankPoemsGroups.TableName+"."+ BankPoemsGroups.PoemColumn)
               .where(Poem.BankGroupColumn+"=?", String.valueOf(groupId));
        return query.execute();
حالا کمی بهتر شد. هم برای تغییر آینده بهتر شد و هم اینکه احتمال خطای تایپی کاهش یافت. ولی باز هم ایجاد کوئری هنوز سخت است و نوشتن مرتب یک رابطه جوین و شرطی و چسباندن مداوم رشته‌ها کار خسته کننده‌ای است و احتمال خطای سهوی و انسانی هم در آن بالاست. برای رفع این مشکل بهتر است یک کلاس جدید برای ساخت این کوئری‌ها داشته باشیم که یک نمونه از آن را در این پایین می‌بینید:
public class QueryConcater {
  public String GetInnerJoinQuery(String table1,String field1,String table2,String field2)
    {
        String query=table1 +"." +field1+"="+table2+"."+field2;
        return query;
    }
......
}
در ادامه برای مرتب سازی و شرط و ... هم می‌نویسیم:
   return new Select()
                .from(Color.class)
                .innerJoin(ProductItem.class)
                .on(queryConcater.GetInnerJoinQuery(ProductItem.TableName,
                        ProductItem.ColorColumn, Color.TableName))
                .where(queryConcater.WhereConditionQuery
                        (ProductItem.TableName, ProductItem.ProductColumn), productId)
                .execute();
در دستورات بالا از این کلاس دو متد برای کوئری جوین و یکی هم برای ساخت شرط ایجاد شده است و مقادیر به صورت پارامتر داده شده‌اند. این الگو کمک میکند که اگر هم این تکه کد اشتباه باشد، با تغییر یکجا بقیه کدها هم تغییر میکنند و اگر در آینده هم ORM تغییر یافت، نحوه کوئری نویسی‌ها در این کلاس تغییر کنند، نه اینکه در طول لایه سرویس پراکنده باشند.

هشت. سعی کنید همیشه از یک سیستم گزارش خطا در اپلیکیشن خود استفاده کنید. در حال حاضر معروفترین سیستم گزارش خطا Acra است که می‌توانید backend آن را هم از اینجا تهیه کنید و اگر هم نخواستید، سایت Tracepot امکانات خوبی را به رایگان برای شما فراهم می‌کند. از این پس با سیستم آکرا شما به یک سیستم گزارش خطا متصلید که خطاهای برنامه شما در گوشی کاربر به شما گزارش داده خواهد شد. این گزارش‌ها شامل:
  • وضعیت گوشی در حین باز شدن برنامه و در حین خطا چگونه بوده است.
  • مشخصات گوشی
  • این خطا به چه تعداد رخ داده است و برای چه تعداد کاربر
  • گزارش گیری بر اساس اولین تاریخ رخداد خطا و آخرین تاریخ، نسخه سیستم عامل اندروید، ورژن برنامه شما و...
و امکانات دیگر.

نه. آکرا همانند Elmah نمی‌تواند خطاهای catch شده را دریافت کند. برای حل این مشکل عبارت زیر را در catch‌ها بنویسید:
ACRA.getErrorReporter().handleException(caughtException);

ده. بر خلاف سیستم دات نت که شما اجباری به استفاده از Try Catch‌ها ندارید. در جاوا اینگونه نیست و هر متدی که Throw روی آن انجام شده باشد مستلزم  استفاده از catch است. به همین دلیل در شماره نه گفتیم که چگونه باید این مشکل را حل کنیم. ولی در بسیاری از اوقات پیش می‌آید که ما داریم از ماژول‌های متفاوتی استفاده میکنیم که جدا از ماژول اصلی برنامه هستند و این مورد باعث می‌شود که بعضی افراد یا Acra را در همه ماژول‌ها صدا بزنند یا اینکه بی خیال آن شوند. ولی کار راحت‌تر این است که شما هم همانند برنامه نویسان جاوا متد خود را به Throw مزین کنید تا در هنگام استفاده از آن در برنامه اصلی نیاز به catch شدن باشد. در واقع شما نباید catch‌ها را داخل یک کتابخانه جدا و مستقل قرار دهید و روش صحیح هم همین است حالا چه استفاده از آکرا نیاز باشد و چه نباشد.

نمونه اشتباه:
 public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) {
        try {
        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
نمونه صحیح:
    public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) throws IOException {

        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
    }
  ادامه دارد...
مطالب
بیلد سیستم گریدل Gradle Build System
امروزه در بسیاری از محیط‌های برنامه نویسی جاوا و اندروید، استفاده از این سیستم رایج است. ولی هنوز دیده می‌شود عده‌ای نسبت به آن دید روشنی ندارند و برای آن‌ها ناشناخته است و در حد یک سیستم کانفیگ آن را می‌شناسند. در این مقاله قصد داریم که مفهوم روشن‌تری از این سیستم را داشته باشم و بدانیم هدف آن چیست و چگونه کار می‌کند تا از این به بعد دیگر آن را به چشم یک کانفیگ کننده‌ی ساده نگاه نکنیم.  قبل از هر چیزی بهتر است که با تعدادی از اصطلاحات آن آشنا شویم تا در متن مقاله به مشکل برنخوریم:

DSL یا Domain Specific languages به معنی زبان‌هایی با دامنه محدود است که برای اهداف خاصی نوشته می‌شوند و تنها بر روی یک جنبه از هدف تمرکز دارند. این زبان‌ها به شما اجازه نمی‌دهند که یک برنامه را به طور کامل با آن بنویسید. بلکه به شما اجازه می‌دهند به هدفی که برای آن نوشته شده‌اند، برسید. یکی از این زبان‌ها همان  css هست که با آن کار میکنید. این زبان به صورت محدود تنها بر روی یک جنبه و آن، تزئین سازی المان‌های وب، تمرکز دارد. در وقع مثل زبان سی شارپ همه منظوره نیست و محدوده‌ای مشخص برای خود دارد. به این نوع از زبان‌های DSL، نوع اکسترنال هم می‌گویند. چون زبانی مستقل برای خود است و به زبان دیگری وابستگی ندارد. ولی در یک زبان اینترنال، وابستگی به زبان دیگری وجود دارد. مثل Fluent Interface‌ها که به ما شیوه آسانی از دسترسی به جنبه‌های یک شیء را می‌دهد. برای آشنایی هر چه بیشتر با این زبان‌ها و ساختار آن، کتاب Domain Specific languages نوشته آقای مارتین فاولر توصیه می‌شود.

Groovy یک زبان شیء گرای DSL هست که برای پلتفرم جاوا ساخته شده است. برای اطلاعات بیشتر در مورد این زبان، صفحه ویکی ، میتواند مفید واقع شود.

از دیرباز سیستم‌های Ant و Maven وجود داشتند و کار آن‌ها اتوماسیون بعضی اعمال بود. ولی بعد از مدتی سیستم Gradle یا جمع کردن نقاط قوت آن‌ها و افزودن ویژگی‌های قدرتمندتری به خود، پا به میدان گذاشت تا راحتی بیشتری را برای برنامه نویس فراهم کند. از ویژگی‌های گریدل می‌توان داشتن زبان گرووی اشاره کرده که قدرت بیشتری را نسبت به سایر سیستم‌ها داشت و مزیت مهم دیگر این بود که انعطاف بالایی را جهت افزودن پلاگین‌ها داشت و گوگل با استفاده از این قابلیت،  پشتیبانی از گریدل را در اندروید استادیو نیز گنجاند تا راحتی بیشتری را در اتوماسیون وظایف سیستمی ایجاد کند. در واقع آنچه شما در سیستم گریدل کار میکنید و اطلاعات خود را با آن کانفیگ میکنید، پلاگینی است که از سمت گوگل در اختیار شما قرار گرفته است و در مواقع خاص این وظایف توسط پلاگین‌ها اجرا می‌شوند.
گریدل به راحتی از سایت رسمی آن قابل دریافت است و می‌توان آن را در پروژه‌های جاوایی که مدنظر شماست، دریافت کنید و با استفاده از خط فرمان، با آن تعامل کنید. هر چند امروزه اکثر ویراستارهای جاوا از آن پشتیبانی می‌کنند.

گریدل یک ماهیت توصیفی دارد که شما تنها لازم است اعمالی را برای آن توصیف کنید تا بقیه کارها را انجام دهد. گریدل در پشت صحنه از یک "گراف جهت دار بدون دور" Directed Acycllic Graph یا به اختصار DAG  استفاده می‌کند و طبق آن ترتیب وظایف یا task‌ها را دانسته و آن‌ها را اجرا می‌کند. گریدل با این DAG، سه فاز آماده سازی، پیکربندی و اجرا را انجام می‌دهد.
  • در مرحله آماده سازی ما به گریدل می‌گوییم چه پروژه یا پروژه‌هایی نیاز به بیلد شدن دارند. در اندروید استادیو،  این مرحله در فایل settings.gradle انجام می‌شود؛ شما در این فایل مشخص می‌کنید چه پروژه‌های نیاز به بیلد شدن توسط گریدل دارند. ساختار این فایل به این شکل است:
include ':ActiveAndroid-master', ':app', ':dbutilities'
در این فایل سه پروژه برای گریدل مشخص شده‌اند. البته از نگاه Intellij سه ماژول معرفی شده‌اند و این فایل برای یک پروژه اختیاری است. گریدل برای پیدا کردن این فایل، از الگوریتم‌های متفاوتی استفاده می‌کند.
  1. در اولین مرحله انتظار دارد که فایل settings در دایرکتوری جاری باشد و اگر آن را پیدا کرد آن را مورد استفاده قرار می‌دهد؛ در غیر اینصورت مرحله بعدی را آغاز می‌کند.
  2. در مرحله دوم،  در این دایرکتوری به دنبال دایرکتوری به نام master میگردد و اگر در آن هم یافت نکرد مرحله سوم را آغاز می‌کند.
  3. در مرحله سوم، جست و جو در دایرکتوری والد انجام می‌شود
  4. چنانچه این فایل را در هیچ یک از احتمالات بالا نیابد، همین پروژه جاری را تشخیص خواهد داد.
اسامی پروژه‌های بالا با این تفکر هستند که در دایرکتوری یا فضای کاری جاری قرار گرفته‌اند که به آن IncludeFlat می‌گویند. ولی چنانچه آدرس پروژه‌ای در وضعیت خاص قرار بگیرد، می‌توان اینگونه مسیر آن را تغییر داد:
include ':ActiveAndroid-master', ':app', ':dbutilities'
project('dbutilities').projectDir=new File(settingsDir,'../dir1/dir2');
الان پروژه dbutiilities در سطح بالاتری از دایرکتوری جاری قرار گرفته است.

  • در مرحله پیکربندی، وظایف یا task‌ها را معرفی می‌کنیم. این عمل پیکربندی توسط فایل build.gradle که برای پروژه اصلی و هر زیر پروژه‌ای که مشخص شده‌اند، صورت می‌گیرد. در این فایل شما می‌توانید خواص و متدهایی را تعریف و و ظایفی را مشخص کنید.
    در پروژه اصلی، فایل BuildGradle شامل خطوط زیر است:
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
    }
}

allprojects {
    repositories {
        jcenter()

    }
}
در کلوژر buildscript مخازنی را که کتابخانه‌های نامبرده (وابستگی ها) در این کلوژ قرار میگیرند، معرفی میکنیم. در کلوژر بعدی تنظمیاتی را که برای همه پروژه‌ها اعمال می‌شوند، انجام می‌دهیم که در آن به معرفی مخزن jcenter پرداختیم. کتابخانه‌ای که در کلوژر buildscript صدا زدیم،  همان پلاگینی است که گوگل برای گریدل منتشر کرده که ما به آن ابزار بیلد می‌گوییم. حال به سراغ دیگر فایل‌های منحصر به فرد هر پروژه بروید. در این فایل‌ها،  شما تنظیمات پیش‌فرضی را می‌بینید که یکی از آن‌ها نسخه بندی پروژه است. پروژه‌های وابسته‌ای را که از مخازن باید دریافت شوند، شامل می‌شود و بسیاری از گزینه‌های دیگری که برای شما مهیا شده است تا در فاز پیکربندی، وظایف را بسازید.

در مرحله اجرا هم این وظایف را اجرا میکنیم.  تمامی این سه عملیات توسط فایل و دستوری به نام gradlew که برگرفته از gradleWrapper می‌باشد انجام می‌شود. اگر در ترمینال اندروید استادیو این عبارت را تایپ کنید، می‌توانید در ادامه دستور پیام‌های مربوط به این عملیات را ببینید و ترتیب اجرای فازها را مشاهده کنید.
بیایید یک task را تعریف کنیم
task mytask <<{
    println ".net tips task in config phase"
}
در ابتدا عبارت task را به عنوان معرفی task می‌آوریم و سپس نام آن را وارد می‌کنیم. بعد از آن ما از عبارت‌های شیفت چپ>> استفاده کردیم. این عبارت شیفت چپ به این معناست که تسک مربوطه را آخر از همه وظایف اجرا کن که این عمل از طریق اجرای متدی به نام doLast صورت میگیرد. اگر در ترمینال اندروید عبارت زیر را تایپ کنید، متن مورد نظر باید نمایش یابد:
gradlew mytask
برای نمایش اطلاعات بیشتر می‌توانید از فلگ info هم استفاده کنید:
gradlew --info mytask
حال شاید بگویید من در بعضی از سایت‌ها یا مستندات و یا پروژه‌های دیگر دیده‌ام که عبارت >> را قرار نمی‌دهند. در این مورد باید گفت که آن‌ها در واقع تسک‌های اجرایی نیستند و برای پیکربندی به کار می‌روند و در فاز پیکربندی هم اجرا می‌شوند که در ادامه نمونه آن را خواهیم دید.
اگر بخواهید خودتان دستی یک تسک پیکربندی را به یک تسک اجرایی تبدیل کنید، می‌توانید متد doLast را صدا بزنید. کد زیر را توسط gradlew اجرا کنید؛ به همراه اطلاعات verbose تا ببینید که هر کدام از پیام‌ها در کدام بخش چاپ می‌شوند. پیام اول در فاز پیکربندی و پیام دوم در فاز اجرایی چاپ می‌شوند.
task mytask {
    println ".net tips task in config phase"

    doLast{
        println ".net tips task in exe phase"
    }
}

یکی از کارهایی که در یک تسک میتوانید انجام دهید این است که آن را به یک تسک دیگر وابسته کنید. به عنوان مثال ما قصد داریم بعد از تسک mytask1،  تسک my task2 اجرا شود و زمان پایان تسک mytask1 را در خروجی نمایش دهیم. برای اینکار باید بین تسک‌ها  یک وابستگی ایجاد شود و سپس با متد doLast کد خودمان را اجرایی نماییم. البته توجه داشته باشید که این وابستگی‌ها تنها به تسک‌های داخل فایل گریدل انجام می‌شود و نه تسک‌های پلاگین‌ها یا وابستگی هایی که تعریف می‌کنیم.
task mytask1 << {
    println ".net tips is the best"
}

task mytask2() {
    dependsOn mytask1

    doLast{
        Date time=Calendar.getInstance().getTime();
        SimpleDateFormat formatter=new SimpleDateFormat("HH:mm:ss , YYYY/MM/dd");
        println "mytask1 is done at " + formatter.format(time);

    }
}
سپس تسک شماره دو را صدا می‌زنیم. در اینجا جون تسک شماره دو به تسک شماره یک وابسته است، ابتدا تسک شماره یک اجرا شده و سپس نوبت تسک شماره دو می‌شود.
gradlew --info mytask2
خروجی کار:
Executing task ':app:mytask1' (up-to-date check took 0.003 secs) due to:
  Task has not declared any outputs.
خروجی تسک شماره یک
.net tips is the best       
:app:mytask1 (Thread[main,5,main]) completed. Took 0.046 secs.
:app:mytask2 (Thread[main,5,main]) started.
:app:mytask2                 
Executing task ':app:mytask2' (up-to-date check took 0.0 secs) due to:
  Task has not declared any outputs.
خروجی تسک شماره دو
mytask1 is done at 04:03:09 , 2016/07/07
:app:mytask2 (Thread[main,5,main]) completed. Took 0.075 secs.
               
BUILD SUCCESSFUL

در گریدل مخالف doLast یعنی doFirst را نیز داریم ولی عملگر جایگزینی برای آن وجود ندارد و مستقیما باید آن را پیاده سازی کنید. خود گریدل به طور پیش فرض نیز تسک‌های آماده ای نیز دارد که می‌توانید در مستندات آن بیابید. به عنوان مثال یکی از تسک‌های مفید و کاربردی آن تسک کپی کردن هست که از طریق آن می‌توانید فایلی یا فایل‌هایی را از یک مسیر به مسیر دیگر کپی کنید. برای استفاده از چنین تسکهایی، باید تسک‌های خود را به شکل زیر به شیوه اکشن بنویسید:
task mytask(type:Copy) {
    dependsOn mytask1

    doLast{
        from('build/apk')
                {
                    include '**/*.apk'
                }
        into '.'
    }

}
در تسک بالا بعد از اجرای تسک شماره یک، آخرین کاری که انجام می‌شود این است که فایل‌های apk موجود در زیر دایرکتوری‌های مسیر from به ریشه اصلی کپی خواهند شد. پس همانطور که می‌بینید گریدل به راحتی عملیات اتوماسیون را انجام می‌دهد.
برای نمایش تسک‌های موجود می‌توانید از گریدل درخواست کنید که لیست تمامی تسک‌های موجود را به شما نشان دهد. برای اینکار می‌توانید دستور زیر را صدا کنید:
gradlew --info tasks
بعد از مدتی تمامی تسک‌های موجود به صورت گروه بندی نمایش داده شده و تسک‌هایی که شما جدیدا اضافه کردید را با عنوان  other tasks نمایش خواهد داد:
Other tasks           
-----------           
clean                 
jarDebugClasses       
jarReleaseClasses     
mytask                
mytask2               
transformResourcesWithMergeJavaResForDebugUnitTest
transformResourcesWithMergeJavaResForReleaseUnitTest
اگر به تسک‌های خود گریدل نگاه کنید برای هر کدام توضیحی هم وجود دارد؛ اگر شما هم قصد دارید توضیحی اضافه کنید از خصوصیت description استفاده کنید:
task mytask(type:Copy) {

    description "copy apk files to root directory"

    dependsOn mytask1

    doLast{
        from('build/apk')
                {
                    include '**/*.apk'
                }
        into '.'
    }

}
یکبار دیگر دستور نمایش تسک‌ها را صدا بزنید تا اینبار توضیح اضافه شده نمایش داده شود.
یکی دیگر از نکات جالب در مورد گریدل این است که می‌تواند برای شما callback ارسال کند. بدین صورت که اگر اتفاقی خاصی افتاد، تسک خاصی را اجرا کند. به عنوان مثال ما در کد پایین تسکی را ایجاد کرده‌ایم که به ما این اجازه را می‌دهد، هر موقع تسکی در مرحله پیکربندی به بیلد اضافه می‌شود، تسک ما هم اجرا شود و نام تسک اضافه شده به بیلد را چاپ می‌کند.
tasks.whenTaskAdded{
    task-> println "task is added $task.name"
}

گریدل امکانات دیگری چون بررسی استثناءها و ایجاد استثناءها را هم پوشش می‌دهد که می‌توانید در این صفحه آن را پیگیری کنید.

Gradle Wrapper
گریدل در حال حاضر مرتبا در حال تغییر و به روز رسانی است و اگر بخواهیم مستقیما با گریدل کار کنیم ممکن است که به مشکلاتی که در نسخه بندی است برخورد کنیم. از آنجا که هر پروژه‌ای که روی سیستم شما قرار بگیرد از نسخه‌ای متفاوتی از گریدل استفاده کند، باعث می‌شود که نتوانید نسخه مناسبی از گریدل را برای سیستم خود دانلود کنید. بدین جهت wrapper ایجاد شد تا دیگر نیازی به نصب گریدل پیدا نکنید. wrapper در هر پروژه میداند که که به چه نسخه‌ای از گریدل نیاز است. پس موقعی که شما دستور gradlew را صدا می‌زنید در ویندوز فایل gradlew.bat صدا زده شده و یا در لینوکس و مک فایل شِل اسکریپت gradlew صدا زده می‌شود و wrapper به خوبی میداند که به چه نسخه‌ای از گریدل برای اجرا نیاز دارد و آن را از طریق دانلود فراهم می‌کند. اگر همینک دایرکتوری والد پروژه اندرویدی خود را نگاه کنید می‌توانید این دو فایل را ببینید.

از آنجا که خود اندروید استادیو به ساخت wrapper اقدام میکند، شما راحت هستید. ولی اگر دوست دارید خودتان برای پروژه‌ای wrapper تولید کنید، مراحل زیر را دنبال کنید:
برای ایجاد wrapper توسط خودتان باید گریدل را دانلود و روی سیستم نصب کنید و سپس دستور زیر را صادر کنید:
gradle wrapper --gradle-version 2.4
دستور بالا یک wrapper برای نسخه 2.4 ایجاد می‌کند.
اگر میخواهید ببینید wrapper که اندروید استادیو شما دارد چه نسخه از گردیل را صدا میزند مسیر را از دایرکتوری پروژه دنبال کنید و فایل زیر را بگشایید:
\gradle\wrapper\gradle-wrapper.properties
هنگامی که گوگل قصد آپدیت نسخه گریدل شما را بکند این فایل را باز کرده و نسخه داخل آن را ویرایش میکند.

این‌ها فقط مختصراتی از آشنایی با نحوه عملکر گریدل برای داشتن دیدی روشن‌تر نسبت به آن بود. برای آشنایی بیشتر با گریدل، باید مستندات رسمی آن را دنبال کنید.
مطالب
AngularJS #2
بهتر است قبل از این که به ادامه‌ی آموزش بپردازم، دو نکته را متذکر شوم:
1) روند آموزشی این فریمورک از کل به جز است؛ به این معنا که ابتدا تمامی قابلیت‌های اصلی فریمورک را به صورت کلی و بدون وارد شدن به جزئیات بیان می‌کنم و پس از آن، جزئیات را در قالب مثال‌هایی واقعی بیان خواهم کرد.
2) IDE مورد استفاده بنده Visual Studio 2012 است. همچنین از ابتدا پروژه را با ASP.NET MVC شروع می‌کنم. شاید بگویید که می‌شود Angular را بدون درگیر شدن با مباحث ASP.NET MVC بیان کرد؛ اما پاسخ من این است که این مثال‌ها باید قابل پیاده‌سازی در نرم‌افزارهای واقعی باشند و یکی از بسترهای مورد علاقه‌ی من ASP.NET MVC است. اگرچه باز هم تاکید می‌کنم که کلیه‌ی مباحث ذکرشده، برای کلیه‌ی زبان‌های سمت سرور دیگر هم قابل استفاده است و هدف من در اینجا بیان یک سری چالش‌ها در ASP.NET MVC است.

نحوه‌ی دریافت AngularJS
1) NuGet Package Manager
2) دریافت از وب‌سایت angularjs.org

دریافت از طریق Nuget Package Manager
روش ارجح افزودن کتابخانه‌های جانبی در یک پروژه‌ی واقعی، استفاده از NuGet Package Manager است. دلیل آن هم بارها بیان‌شده است از جمله: باخبر شدن از آخرین به‌روزرسانی کتابخانه‌ها، دریافت وابستگی‌های کتاب‌خانه‌ی مورد نظر و نبودن محدودیت تحریم برای دریافت فایل‌ها است.
روش کار هم بسیار ساده است، کافی است که بر روی پروژه کلیک راست کرده و گزینه‌ی Manage NuGet Packages را انتخاب کنید و با جست جو angularjs نسبت به نصب آن اقدام نمایید.
اگر هم با ابزارهای گرافیکی رابطه‌ی خوبی ندارید، می‌توانید از Package Manager Console فراهم‌شده توسط NuGet استفاده کنید. کافی است در کنسول پاورشل آن عبارت زیر را تایپ کنید:
Install-Package angularjs

پس از نصب angularjs، شاهد تغییراتی در پوشه‌ی Scripts پروژه‌ی خودخواهید بود. تعداد زیادی فایل جاوا اسکریپت که با عبارت angular شروع‌شده‌اند، به این پوشه اضافه شده است. در حال حاضر ما تنها به فایل angular.js نیاز داریم و احتیاجی به فایل‌های دیگر نیست.
همچنین یک پوشه به نام i18n نیز اضافه شده است که برای مباحث Globalization و Internationalization به کار گرفته می‌شود. 
 
دریافت از سایت angularjs.org
برای دریافت Angular از وب سایت رسمی‌اش، به angularjs.org مراجعه کنید؛ اما گویا به دلیل تحریم‌ها این سایت برای IP ایران مسدود شده است (البته افرادی نیز بدون مشکل به آن دسترسی دارند). دکمه‌ی Download را فشار داده و در نهایت کلید دریافت را بزنید. اگر نسخه‌ی کامل آن را دریافت کنید، لیستی از مستندات AngularJS را نیز در فایل دریافتی، خواهید داشت. در هر صورت این روش برای استفاده از angular دریک پروژه‌ی واقعی توصیه نمی‌شود.
پس به عنوان یک best practice، همیشه کتاب‌خانه‌های جانبی را با NuGet دریافت و نصب کنید. رفع موانع تحریم‌ها، یکی از مزایای مهم آن است.
   
پس از دریافت angular،  نوشتن برنامه‌ی معروف Hello, World به وسیله‌ی آن ، می‌تواند بهترین شروع باشد؛ اما اگر اجازه بدهید، نوشتن این برنامه را در قالب توضیح قالب‌های سمت کلاینت انجام دهیم.
   
قالب‌های سمت کلاینت (Client Side Templates)
در برنامه‌های وب چند صفحه‌ای و یا اکثر وب سایت‌های معمول، داده‌ها و کدهای HTML، در سمت سرور اصطلاحا سرهم و مونتاژ شده و خروجی نهایی که HTML خام است به مرورگر کاربر ارسال می‌شود. با یک مثال بیشتر توضیح می‌دهم: در ASP.NET MVC معمولا از لحظه‌ای که کاربر صفحه‌ای را درخواست می‌کند تا زمانی که پاسخ خود را در قالب HTML می‌بیند، این فرآیند طی می‌شود: ابتدا درخواست به Controller هدایت می‌شود و سپس اطلاعات مورد نیاز از پایگاه داده خوانده‌شده و در قالب یک Model به View که یک فایل HTML ساده است، منتقل می‌شود. سپس به کمک موتور نمایشی Razor، داده‌ها در جای مناسب خود قرار می‌گیرند و در نهایت، خروجی که HTML خام است به مرورگر کلاینت درخواست‌کننده ارسال می‌شود تا در مرورگر خود نتیجه را مشاهده نماید. روال کار نیز در اکثر SPA‌های معمول و یا اصطلاحا برنامه‌های AJAX، باکمی تغییر به همین شکل است.
 اما در Angular داستان به شکل دیگری اتفاق می‌افتد؛ Angular قالب HTML و داده‌ها را به صورت جداگانه از سرور دریافت می‌کند و در مرورگر کاربر آن‌ها را سرهم و مونتاژ می‌کند. بدیهی است که در اینجا قالب، یک فایل HTML ساده و داده‌ها می‌تواند به فرم JSON باشد. در نتیجه کار سرور دیگر فراهم کردن قالب و داده‌ها برای کلاینت است و بقیه‌ی ماجرا در سمت کلاینت رخ می‌دهد.
خیلی خوب، مزیت این کار نسبت به روش‌های معمول چیست؟ اگر اجازه بدهید این را با یک مثال شرح دهم:
در بسیاری از سایت ها، ویژگی ای به نام اسکرول نامحدود وجود دارد. در همین سایت نیز دکمه ای با عنوان بیشتر در انتهای لیستی از مطالب، برای مشاهده‌ی ادامه‌ی لیست قرار گرفته است. سعی کنید پس از فشردن دکمه‌ی بیشتر، داده‌های دریافتی از سرور را مشاهده کنید. پس از انجام این کار مشاهده خواهید کرد که پاسخ سرور HTML خام است. اگر تعداد 10 پست از سرور درخواست شود، 10 بار محتوای HTML تکراری نیز دریافت خواهد شد؛ در صورتی که ساختار HTML یک پست هم کفایت می‌کرد و تنها داده‌ها در آن 10 پست متفاوتند؛ چرا که قالب کار مشخص است و فقط به ازای هر پست باید آن داده‌ها در جای مناسب خود قرار داد.
دیدگاه‌های یک پست هم به خوبی با Angular قابل پیاده سازی است. قالب HTML یک دیدگاه را برای angular تعریف کرده و داده‌های مناسب که احتمالا JSON خام است از سرور دریافت شود. نتیجه‌ی این کار هم صرفه جوی در پهنای باند مصرفی و افزایش فوق العاده‌ی سرعت است، همچنین در صورت نیاز می‌توان داده‌ها و قالب‌ها راکش کرد تا مراجعه به سرور به حداقل برسد.
 چگونگی انجام این کار در AngularJs به صورت خلاصه به این صورت است که در angular یک directive به نام ng-repeat تعریف شده است که مانند یک حلقه‌ی foreach برای HTML عمل می‌کند. شما در داخل حلقه، قالب را مشخص می‌کنید و به ازای تعداد داده‌ها، آن حلقه تکرار می‌شود و بر روی داده‌ها پیمایش صورت می‌گیرد.
البته این مثال‌ها فقط دو نمونه از کاربرد این ویژگی در دنیای واقعی بود و مطمئن باشید که در مقالات آینده مثال‌های زیادی از این موضوع را پیاده‌سازی خواهیم کرد.
بهتر است که دیگر خیلی وارد جزئیات نشویم و اولین برنامه‌ی خود را به کمک angularjs بنویسیم. این برنامه، همان برنامه‌ی معروف Hello ,World است؛ اما در این برنامه به جای نوشتن یک Hello, World ساده در صفحه، آن را با ساختار angularjs پیاده‌سازی می‌کنیم.
در داخل ویژوال استادیو یک فایل HTML ساده ایجاد کنید و کد‌های زیر را داخل آن بنویسید.
<!DOCTYPE html>
<html ng-app>
<head>
    <title>Sample 1</title>
</head>
<body>
    <div ng-controller="GreetingController">
        <p>{{greeting.text}}, World!</p>
    </div>

    <script src="../Scripts/angular.js"></script>
    <script>
        function GreetingController($scope) {
            $scope.greeting = {
                text: "Hello"
            };
        }
    </script>
</body>
</html>
سپس فایل فوق را در مرورگر اجرا کنید. بله؛ عبارت Hello, World را مشاهده خواهید کرد. یک بار دیگر خاصیت text  را در scope.greeting$ به hi تغییر بدهید و باز هم نتیجه را مشاهده کنید.
این مثال در نگاه اول خیلی ساده است، اما دنیایی از مفاهیم angular را در بر دارد. شما خواص جدیدی را برای عناصر HTML مشاهده می‌کنید: ng-app، ng-controller، آکلود‌ها و عبارت درون آن و متغیر scope$ به عنوان پارامتر.
حال بیایید ویژگی‌ها و مفاهیم جالب کدهای نوشته شده را بررسی کنیم؛ چرا که فرصت برای بررسی ng-app و بقیه‌ی موارد نا آشنا زیاد است:

- هیچ id و یا class برای عناصر html در نظر گرفته نشده تا با استفاده از آنها، رویدادی را برای عناصر مورد نظر مشخص کنیم.

- وقتی در GreetingController مقدار greeting.text را مشخص کرده ایم، باز هم هیچ رویدادی را صدا نزده و یا مشخص نکرده ایم.

- GreetingController یک کلاس ساده‌ی جاوا اسکریپت (POJO) است و از هیچ چیزی که توسط angular فراهم شده باشد، ارث بری نکرده است.

- اگر به متد سازنده‌ی کلاس GreetingController دقت کنید، متغیر scope$ به عنوان پارامتر تعریف شده است. نکته‌ی جالب این است که ما هیچ گاه به صورت دستی سازنده‌ی کلاس GreetingController را صدا نزده ایم و حتی درون سازنده هم scope$ را ایجاد نکرده ایم؛ پس چگونه توانسته ایم خاصیتی را به آن نسبت داده و برنامه به خوبی کار کند. بهتر است برای پاسخ به این سوال خودتان دست به کار شوید؛ ابتدا نام متغیر scope$ را به نام دلخواه دیگری تغییر دهید و سپس برنامه را اجرا کنید. بله برنامه دیگر کار نمی‌کند. دلیل آن چیست؟ همان طور که گفتم Angular دارای یک سیستم تزریق وابستگی توکار است و در اینجا نیز scope$ به عنوان وابستگی در سازنده‌ی  این کلاس مشخص شده است تا نمونه‌ی مناسب آن توسط angular به کلاس GreetingController ما تزریق شود؛ اما چرا به نام آن یعنی scope$ حساس است؟ به این دلیل که زبان جاوا اسکریپت یک زبان پویا است و نوع در آن مطرح نیست؛ angular مجبور است که از نام پارامترها برای تزریق وابستگی استفاده می‌کند. در مقالات آینده چگونگی عملکرد سیستم تزریق وابستگی angular را به تشریح بیان می‌کنم.

- همچنین همان طور که در مورد قبلی نیز به آن اشاره کردم، ما هیچ گاه خود دستی سازنده‌ی GreetingController را صدا نزدیم و جایی نیز نحوه‌ی صدا زدن آن را مشخص نکرده ایم.

 تا همین جا فکر کنم کاملا برای شما مشخص شده است که ساختار فریمورک Angular با تمامی کتاب خانه‌های مشابه متفاوت است و با ساختاری کاملا اصولی و حساب شده طرف هستیم. همچنین در مقالات آینده توجه شما را به قابلیت‌هایی بسیار قدرتمند‌تر جلب خواهم کرد.
   
MVC ،MVP ، MVVM و یا MVW
 در بخش اول این مقاله، الگوی طراحی پیشنهادی فریمورک Angular را MVC بیان کرده‌ام؛ اما همان طور که گفته بودم AngularJS از انقیاد داده دوطرفه (Two Way Data Binding) نیز به خوبی پشتیبانی می‌کند و به همین دلیل عده ای آن را یک MVVM Framework تلقی می‌کنند. حتی داستان به همین جا ختم نمی‌شود و عده ای آن را به چشم MVP  Framework  نیز نگاه می‌کنند. در ابتدا سایت رسمی AngularJS الگوی طراحی مورد استفاده را MVC بیان می‌نمود ولی در این چند وقت اخیر عنوانش را به MVW Framework تغییر داده است.
MVW مخفف عبارت Model View Whatever هست و کاملا مفهومش مشخص است. Model و View بخش‌های مشترک تمام الگو‌ها بودند و تنها بخش سوم مورد اختلاف توسعه دهندگان بود؛ در نتیجه انتخاب آن را بر عهده‌ی استفاده کننده قرار داده اند و تمام امکانات لازم برای پیاده‌سازی این الگو‌های طراحی را فراهم کرده اند. در طی این مقالات صرف نظر از تمام الگوهای طراحی فوق، من بیشتر بر روی MVC تمرکز خواهم کرد.
الگوی طراحی MVC در سال 1970 به عنوان بخشی از زبان برنامه نویسی Smalltalk معرفی شد و از همان ابتدا به سرعت محبوبیت زیادی در بین محیط‌های توسعه‌ی دسکتاپی از قبیل ++C و Java  که رابط کاربری گرافیکی به نوعی در آن‌ها دخیل است، پیدا کرد.
تفکر MVC این را بیان می‌کند که باید جداسازی واضح و روشنی بین مدیریت داده‌ها (Model)، منطق برنامه (Controller) و نمایش داده‌ها به کاربر (View) وجود داشته باشد و در اصل هدفش جداسازی اجزای رابط کاربری به بخش هایی مجزا است.
     
شاید این سوال برای شما پیش بیاید که چرا باید چنین الگویی را در برنامه‌ها پیاده کرد؟
احتمالا تا کنون از بین برنامه هایی که نوشته اید، رابط کاربری بیشتر از آن‌ها را نیز خودتان مجبور شده اید طراحی کنید؛ به این دلیل که برنامه‌ی شما بدون رابط کاربری قابل اجرا شدن نبوده است. اجرای برنامه‌ی شما منوط به وجود تعدادی دکمه و textbox و ... بوده است و به قولی منطق برنامه به رابط گرافیکی گره خورده بوده است. پس می‌توان گفت که پیاده‌سازی الگوی طراحی وقتی ضرورت پیدا می‌کند که رابط گرافیکی، قسمتی از برنامه‌ی شما را تشکیل دهد.
آیا با وجود زبان‌های طراحی ساده ای مثل HTML و XAML و ... احتیاجی است که برنامه نویس وقت خود را صرف طراحی رابط کاربری کند؟ مسلما خیر، چون دیگر با این امکانات یک طراح هم از پس این کار به خوبی و یا حتی بهتر بر می‌آید. دیگر وظیفه‌ی برنامه نویس نوشتن کد‌های مربوط به منطق برنامه است. کدهایی که بدون UI هم قابل تست شدن باشد و به راحتی بتوان برای آن‌ها آزمون‌های واحد نوشت. برنامه نویس باید این را در نظر بگیرد که UI وجود ندارد و حتی ممکن است هیچ گاه هم ایجاد نشود و  این کد‌ها تبدیل به یک کتابخانه شود و مورد استفاده قرار بگیرد تا در یک برنامه با رابط کاربری گرافیکی.
در MVC، روال عمومی کار به این شکل است که View داده‌ها را از Model دریافت می‌کند و به کاربر نمایش می‌دهد. وقتی که کاربر با کلیک کردن و تایپ کردن با برنامه ارتباط برقرار می‌نماید، Controller به این درخواست‌ها پاسخ می‌دهد و داده‌های موجود در Model را به روز رسانی می‌کند. در نهایت هم Model  تغییرات خود را به View منعکس می‌کند تا View آن چه را که پیش از آن نمایش می‌داده است، تغییر دهد و View را از تغییرات رخ داده آگاه نماید.
اما در برنامه‌های Angular قضیه از چه قرار است؟ در Angular، قالب HTML  یا اگر بخواهم دقیق‌تر بگویم (Document Object Model(DOM معادل View است؛ کلاس‌های جاوا اسکریپتی نقش Controller را دارند؛ و خواص اشیای جاوا اسکریپتی و یا حتی خود اشیا نقش Model را بر عهده دارند.

ساختار بخشیدن به برنامه با استفاده MVC یک مزیت مهم دیگر نیز دارد: ساختار کار کاملا مشخص است و هر کسی نمی‌تواند به صورت سلیقه ای آن را پیاده سازی کند. با یک مثال این موضوع را تشریح می‌کنم: اگر کسی پروژه‌ی بنده را که با ASP.NET MVC نوشتم، بررسی کند، اصلا احساس غریبی نمی‌کند و به راحتی می‌تواند آن را توسعه دهد. دلیل این موضوع این است که ASP.NET MVC یک ساختار مشخص را به توسعه دهندگان اجبار کرده است و هر کسی این ساختار را رعایت کند و با آن آشنا باشد، به راحتی می‌تواند با آن کار کند. توسعه دهنده می‌داندکه من Model را کجا تعریف کرده ام، Controller مربوط به هر View کجاست و در کدام قسمت با پایگاه داده ارتباط برقرار کرده‌ام؛ اما در مورد کد‌های JavaScript و سمت کلاینت چه طور؟ توسعه دهنده ای که می‌خواهد کار من را ادامه بدهد دچار وحشت می‌شود! الگوی مشخصی وجود ندارد؛ معلوم نیست که کجا DOM را دستکاری کرده‌ام، در کدام قسمت با سرور ارتباط برقرار شده و... به قول معروف با یک اسپاگتی کد تمام عیار طرف می‌شود. AngularJS این مشکل را حل نموده و ساختار خاصی را سعی کرده به شما دیکته کند و تا حد ممکن دست شما را نیز باز گذاشته است. جدا از همه‌ی اینها، برنامه‌های مبتنی بر Angular به راحتی نگه داری  و تست می‌شوند و بدون هیچ دغدغه ای آن‌ها را می‌توان توسعه داد.
   
در حاشیه
شاید در هنگام دریافت فایل angularjs و افزودن آن به پروژه‌ی خود شروع به اعتراض کرده اید که نسخه‌ی فشرده شده‌ی آن 87 کیلو بایت حجم دارد در صورتی که این حجم در کتابخانه‌های مشابه ممکن است حتی به 10 کیلوبایت هم نرسد. اگر دقت کرده باشید من در بیان AngularJS از واژه‌ی کتاب خانه استفاده نکردم و فقط از واژه‌ی فریمورک استفاده کردم. بله نمی‌شود angular را با کتاب خانه هایی مقایسه کرد که مهمترین ویژگی خود را Data Binding می‌دانند. AngularJS یک بستر کاری قدرتمند است که تمام راه حل‌های موجود را در خود جمع کرده است. تیم توسعه دهنده‌ی آن هم هیچ ادعایی ندارد و می‌گویند که ما هیچ چیزی را خودمان اختراع نکرده ایم، بلکه راه حل‌های عالی را برگزیدیم، تفکرهای خوب را ارتقا بخشیده و در فریمورک خود استفاده کردیم و حتی از ایده‌های خوب دیگر کتاب خانه‌ها هم استفاده کرده ایم. بنابر این نباید به حجم آن در مقابل توانایی هایی که دارد اعتراض کرد.
   
همچنین به نظر می‌آید که AngularJS یک فریمورک پیچیده است. ولی من همیشه بین پیچیده و پیچیده شده تفاوت قائل می‌شوم. به نظر شخصی خودم Angular به دلیل مشکلات خاص و پیچیده ای که حل می‌کند پیچیده است و پیچیده شده نیست. اگر آن را پیچیده شده حس می‌کنید، تنها دلیلش، نحوه‌ی آموزش دادن بنده است، تمام سعی خود را می‌کنم که مفاهیم را تا حد ممکن ساده بیان کنم و امیدوارم در آینده که با مثال‌های بیشتری روبرو می‌شوید، این مفاهیم به کارتان بیاید.
     
در مقاله‌ی بعدی به مفاهیم انقیاد داده، تزریق وابستگی، هدایت گر‌ها (Directives) و سرویس‌ها در AngularJS می‌پردازم.