Quantcast
Channel: ‫فید مطالب .NET Tips
Viewing all 1980 articles
Browse latest View live

‫ارتقاء به Angular 6: بررسی تغییرات Angular CLI

$
0
0
اولین مرحله‌ی ارتقاء به Angular 6، به روز رسانی Angular CLI 1.x به نگارش 6 آن است. این شماره نگارش نیز با شماره نگارش Angular یکی شده‌است و دیگر 1x نیست. CLI 6.0 فقط پروژه‌های Angular 5.x و 6x را پشتیبانی می‌کند و برای نصب آن نیاز به حداقل NodeJS 8.9 و NPM 5.5 را خواهید داشت. بنابراین ابتدا دستورات زیر را صادر کرده و اگر هنوز از نگارش‌های قدیمی این ابزارها استفاده می‌کنید، قبل از هر کاری باید آن‌ها را به روز رسانیکنید:
node -v
npm -v
این نگارش از CLI در پشت صحنه از Webpack 4 استفاده می‌کند که نسبت به نگارش‌های پیشین آن بسیار سریعتر است، tree-shaking بهتری را انجام می‌دهد و در نهایت سبب تولید برنامه‌هایی با حجم کمتر و با سرعت build بیشتری خواهد شد.


فایل پیشین angular-cli.json حذف و فایل جدید angular.json بجای آن معرفی شده‌است

یکی از مهم‌ترین تغییرات CLI 6.0 نسبت به نگارش‌های قبلی آن، پشتیبانی از چندین پروژه است و به همین منظور ساختار فایل تنظیمات آن‌را به طور کامل تغییر داده‌اند و اگر دستور ng new project1 را صادر کنید، دیگر از فایل پیشین angular-cli.json خبری نبوده و بجای آن فایل جدید angular.json قابل مشاهده‌است:
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "project1": {
در اینجا ساختار ابتدای فایل angular.json را مشاهده می‌کنید که در آن مفهوم projects (و یا workspace در اینجا) به همراه project1 جدیدی که ایجاد کردیم، به عنوان زیر مجموعه‌ی آن قابل مشاهده‌است.
مزیت مهم این قابلیت، امکان ایجاد libraries است که به صورت توکار از این نگارش پشتیبانی می‌شود و می‌توان اجزایی مانند components ،directives ،pipes و services اشتراکی را در یک یا چندین کتابخانه قرار داد و سپس از آن‌ها در پروژه‌ی اصلی و یا چندین پروژه‌ی متصل استفاده کرد.
نگارش 6 در پشت صحنه، پروژه‌ی موفق ng-packagrرا به مجموعه‌ی CLI اضافه کرده‌است و از این پس توسط خود CLI می‌توان کتابخانه‌های استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانه‌های npm حاصل را نیز به همراه دارد؛ مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار، با بسته‌های فعلی npm مخصوص Angular وجود دارند. برای مثال بدون استفاده‌ی از این ابزار، نیاز است مستندات 13 صفحه‌ایساخت کتابخانه‌های Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0، به صورت خودکار ایجاد و مدیریت می‌شود.

بنابراین اکنون سؤال مهمی که مطرح می‌شود این است: آیا باید فایل angular-cli.json پیشین را به صورت دستی به این فایل جدید به روز رسانی کرد و چگونه؟


به روز رسانی تمام بسته‌های سراسری سیستم

در ادامه پیش از هر کاری نیاز است تمام بسته‌های سراسری npm ایی را که هر از چندگاهی به سیستم خود اضافه کرده‌اید، به روز رسانی کنید. برای مشاهده‌ی لیست موارد تاریخ مصرف گذشته‌ی آن‌ها، دستور زیر را صادر کنید:
 npm outdated -g --depth=0
و برای به روز رسانی یکجای آن‌ها نیاز است دستور زیر اجرا شود:
 npm update -g


به روز رسانی خودکار ساختار فایل angular-cli.json

این به روز رسانی توسط CLI 6.0 به صورت خودکار پشتیبانی می‌شود و شامل این مراحل است:
ابتدا نیاز است بسته‌ی سراسری آن‌را به روز رسانی کرد:
 npm i -g @angular/cli
سپس وارد پوشه‌ی اصلی پروژه‌ی خود شده و این دستور را به صورت محلی نیز وارد کنید:
 npm install --save-dev @angular/cli@latest
با اینکار بسته‌ی CLI محلی پروژه به روز شده و اکنون می‌توانیم از قابلیت جدید آن که ng update نام دارد، استفاده کنیم. برای این منظور دستورات ذیل را به ترتیب اجرا کنید:
ng update @angular/cli
ng update @angular/core
ng update rxjs
دستور اول کار تبدیل خودکار فایل angular-cli.json قدیمی را به ساختار جدید آن انجام میدهد؛ با این لاگ:
DELETE .angular-cli.json
CREATE angular.json (4273 bytes)
UPDATE karma.conf.js (1008 bytes)
UPDATE src/tsconfig.spec.json (322 bytes)
UPDATE package.json (2076 bytes)
UPDATE tslint.json (3217 bytes)
دستور دوم بسته‌های هسته‌ی angular را به روز رسانی می‌کند و دستور سوم کار به روز رسانی کتابخانه‌ی rxjs را انجام می‌دهد.
لیست سایر بسته‌هایی را که می‌توان توسط این دستور به روز رسانی کرد، با اجرا دستور ng update می‌توانید مشاهده کنید. برای مثال اگر از Angular Material نیز استفاده می‌کنید، دستور به روز رسانی آن به صورت زیر است:
 ng update @angular/material


مشکل! دستور ng update کار نمی‌کند!

اگر پروژه‌ی شما صرفا مبتنی بر بسته‌های اصلی Angular باشد، مراحل یاد شده‌ی فوق را با موفقیت به پایان خواهید رساند. اما اگر از کتابخانه‌های ثالثی استفاده کرده باشید، منهای دستور «ng update @angular/cli» که کار تولید فایل جدید angular.json را انجام می‌دهد، مابقی با خطاهایی مانند «incompatible peer dependency» و یا «Invalid range:>=2.3.1 <3.0.0||>=4.0.0» متوقف می‌شوند.
در یک چنین حالتی نیاز است ابتدا وابستگی‌های محلی پروژه را به روز کرد و سپس دستورات ng update را تکرار نمود. برای این منظور ابتدا بسته‌ی npm-check-updates را نصب کنید:
 npm install npm-check-updates -g
کار آن به روز رسانی خودکار بسته‌های npm یک پروژه است. پس از آن دستورات زیر را صادر نمائید:
ncu -u
npm install
دستور اول تمام شماره نگارش‌های بسته‌های موجود در فایل package.json را به صورت خودکار به آخرین نگارش آن‌ها روز رسانی می‌کند و دستور دوم این بسته‌های جدید را دریافت و نصب خواهد کرد.
اکنون تمام وابستگی‌های محلی پروژه‌ی شما به صورت خودکار به آخرین نگارش آن‌ها به روز رسانی شده‌اند و اینبار اگر دستورات ذیل را اجرا کنید، با خطاهای یاد شده مواجه نخواهید شد:
ng update @angular/core
ng update rxjs

البته اگر در این حالت برنامه را کامپایل کنید، کار نخواهد کرد. علت اصلی آن به به‌روز رسانی rxjs به نگارش 6 آن مرتبط می‌شود که در مطلب بعدی پیگیری خواهد شد و این نگارش شامل حذفیات بسیاری است در جهت کاهش حجم آن، یکپارچکی و یک دست شدن syntax آن و همچنین بهبود قابل ملاحظه‌ی کارآیی آن. البته پیشنیاز الزامی آن آشنایی با pipe-able operatorsاست. علت دیگر کامپایل نشدن برنامه هم می‌تواند عدم استفاده از HttpClientنگارش 4.3 به بعد باشد.


خاموش کردن اخطار «TypeScript version mismatch»

اگر نگارش TypeScript نصب شده‌ی در سیستم به صورت سراسری، با نگارش محلی پروژه‌ی شما یکی نباشد، اخطار «TypeScript version mismatch» را دریافت می‌کنید. روش خاموش کردن آن در CLI جدید با اجرای دستور زیر است:
 ng config cli.warnings.typescriptMismatch false
البته نگارش 6 نیاز به TypeScript 2.7.2 ذکر شده‌ی در package.json پروژه‌ی محلی را دارد؛ وگرنه اصلا شروع به کامپایل برنامه نمی‌کند:
{
  "devDependencies": {
    "typescript": "~2.7.2"
  }
}
بهترین راه برای یافتن این شماره، اجرای دستور ng new projet1 در یک پوشه‌ی خالی است و سپس مقایسه‌ی محتوای فایل package.json آن با فایل پروژه‌ی موجود.


 تغییرات ng build در نگارش 6

در نگارش 6، مفهوم پیشین environments به configurations تغییر یافته‌است و اینبار در فایل جدید angular.json تنظیم می‌شوند:
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "angular-template-driven-forms-lab": {
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ]
            }
          }
        },
و پیشتر اگر برای تنظیم محیط build از سوئیچ env استفاده می‌شد:
 ng build --env staging
اکنون این سوئیچ به configuration تغییر یافته‌است و نام آن از مداخل configurations فایل angular.json، مانند مثال فوق که «production» نام دارد، باید دریافت و تنظیم شود:
 ng build --configuration staging
و یا برای مثال دستور «ng build --env=prod» دیگر اجرا نمی‌شود و env=prod حذف شده‌است و اکنون اجرای  ng build --prod مانند اجرای دستور ng build --configuration=prod است.


تغییرات نام‌های نهایی تولیدی

در CLI 6.0، نام‌های نهایی تولیدی دیگر به همراه bundle  و یا chunk نیستند. برای مثال دستور ng build یک چنین خروجی را تولید می‌کند:
>ng build --watch

Date: 2018-05-05T09:10:50.158Z
Hash: a43eab94ff01539b8592
Time: 31733ms
chunk {main} main.js, main.js.map (main) 9.38 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 226 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 5.4 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 15.6 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.05 MB [initial] [rendered]

و هش‌های حالت prod به صورت زیر تولید و به نام فایل اضافه می‌شوند:
>ng build --prod --watch

Date: 2018-05-05T09:17:01.803Z
Hash: f25fd6788a4969c52b70
Time: 73279ms
chunk {0} runtime.6afe30102d8fe7337431.js (runtime) 1.05 kB [entry] [rendered]
chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered]
chunk {2} polyfills.6c08419970f9e4781b69.js (polyfills) 59.4 kB [initial] [rendered]


ساده شدن افزودن وابستگی‌های ثالث به پروژه‌های CLI

برای نصب یک کتابخانه‌ی ثالث، پیشتر می‌بایستی ابتدا بسته‌ی npm آن جداگانه نصب و سپس فایل config برنامه، جهت معرفی مداخل آن، ویرایش می‌شد. اکنون دستور جدید ng add تمام این مراحل را به صورت خودکار انجام می‌دهد:
 ng add @angular/material
برای نمونه دستور فوق نه تنها فایل‌های Angular Material را دریافت می‌کند، بلکه فایل‌های CSS و ماژول‌های مرتبط با آن‌را نیز import خواهد کرد. البته این مورد از کلید ng-add فایل package.json بسته‌ی در حال نصب دریافت و تنظیم می‌شود و کتابخانه‌های جدید باید خود را بر این اساس وفق دهند.

‫بررسی کلمات کلیدی Const و ReadOnly در سی شارپ

$
0
0
تعریف: Constant فیلدی است که مقدار آن در زمان کامپایل (Compile time) مشخص می‌شود و این مقدار هیچگاه نمی‌تواند تغییر کند (ثابت است). از کلمه کلیدی (Keyword) ، const برای تعریف یک constant استفاده می‌شود.

  تعاریف اولیه :
Constant Field :فیلد ثابتی که مستقیما در یک Class و یا Struct تعریف می‌شود.
Constant Local :ثابتی که در بلاک‌های برنامه (بدنه یک تابع ، حلقه تکرار و ...) تعریف می‌شود.

همه‌ی انواع درون ساخت (Built in) در زبان #C مانند (انواع عددی، بولین، کاراکتر، رشته و نوع‌های شمارشی) و اشاره‌گرهای تهی (null reference) می‌توانند بصورت constant تعریف شوند. باید توجه داشت که عبارت تعریف و مقدار دهی یک constant (ثابت) باید بصورتی باشد که در زمان کامپایل کاملا قابل ارزیابی باشد.

جدول مقایسه‌ای بین Const و ReadOnly
Constant
ReadOnly
میتواند به Field‌ها و همچنین local‌‌ها اعمال شود. تنها به Field ها  اعمال می‌شود. 
مقدار دهی اولیه آن الزامی است. 
مقدار دهی اولیه می‌تواند هنگام تعریف و یا در درون سازنده انجام شود (در هیچ متد دیگری امکان پذیر نیست). 
 تخصیص حافظه انجام نمی‌شود و مقدار آن در کد‌های IL گنجانده می‌شود (توضیح در ادامه مطلب).   تخصیص حافظه بصورت داینامیک انجام می‌شود و می‌توانیم در زمان اجرا مقدار آن را بدست آوریم. 
 ثابت‌ها در #C بصورت پیش فرض از نوع static هستند. بدین معنا که از طریق نام کلاس  قابل دسترسی هستند.   تنها از طریق وهله سازی از یک کلاس قابل دسترسی هستند. 
 نوع‌های درون ساز (built in) و Null Reference ها  را می‌توان بصورت const تعریف کرد.
Boolean,Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal , string. 
مشابه Constant ها
مقدار آن در طول عمر یک برنامه ثابت است.
 مقدار آن می‌تواند در هنگام فراخوانی سازنده برای وهله‌های مختلف متفاوت باشد.
فیلد‌های const را نمی‌توان بصورت پارامتر‌های out و ref استفاده کرد.
فیلد‌های ReadOnly را می‌توان بصورت پارامتر‌های ref و out در درون سازنده استفاده کرد. 

نحوه تعریف یک constant :





همانطور که در تصویر مشاهده می‌کنید در کنار نماد انتخابی برای const‌ها یک قفل کوچک (نشان از غیرقابل تغییر بودن) قرار گرفته است .


مثالی از تعریف و رفتار Constant‌ها در #C :
const int field_constant = 10;  //constant field
static void Main(string[] args)
{
    const int x = 10, y = 15;   //constant local :correct
    const int z = x + y;        //constant local : correct;
    const int a = x + GetVariableValue();//Error
}
public static int GetVariableValue()
{
    const int localx = 10;
    return 10;
}
در خطوط اول و دوم ارزش متغیر‌های x,y,z بدرستی محاسبه و ارزیابی شده‌است. اما در خط سوم تخصیص مقدار برای ثابت a به زمان اجرای برنامه موکول شده است. در نتیجه با بروز خطا مواجه می‌شویم .

فیلد‌های فقط خواندنی ReadOnly


در #C فقط Field‌‌ها را می‌توان بصورت ReadOnly  تعریف کرد. این فیلد‌ها یا در زمان تعریف و یا از طریق سازنده مقدار دهی می‌شوند.






بررسی تفاوت readonly و  const در سطح IL

برای مشاهده کدهای سطح میانی (IL Code) از ابزار خط فرمان Developer Command ویژوال استدیو 2017 و همچنین برنامه ILdasm استفاده شده است. همانطور که در جدول مقایسه‌ای بیان شد، برای constant field ها  تخصیص حافظه‌ای صورت نمی‌گیرد و مقادیر مستقیما در کد‌های IL گنجانده می‌شود.
مثال: 
 class Program
    {
        public const int numberOfDays = 7;
        public readonly double piValue = 3.14;

        static void Main(string[] args)
        {
            
        }
    }












اگر فایل Exe کد فوق را توسط نرم افزار IL Dasm مشاهده کنید، خواهید دید که مقدار ذخیره شده در numberOfDays در کد IL گنجانده شده است : 








ولی مقدار ذخیره شده در piValue در زمان اجرا قابل دسترسی می‌باشد.






مشکل Versioning فیلدهای const
public const int numberOfDays = 7;
public readonly double piValue = 3.14;
اگر کد‌های فوق را به یک اسمبلی مجزا منتقل کنیم و از این کد‌ها در پروژه‌ای جدید استفاده کنیم، وضعیت Code ‌های IL به صورت زیر است:
کد برنامه اصلی که ارجاعی به اسمبلی جانبی دارد:
static void Main(string[] args)
{
   var readEx = new MyLib.TestClass();
   var readConstValue = MyLib.TestClass.numberOfDays;
   var readReadOnlyValue = readEx.piValue;
}
کد‌های IL :
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size 17 (0x11)
  .maxstack  1
  .locals init ([0] class [MyLib]MyLib.TestClass readEx,
  [1] int32 readConstValue,
  [2] float64 readOnlyValue)
  IL_0000:  nop
  IL_0001:  newobj   instance void [MyLib]MyLib.TestClass::.ctor()
  IL_0006:  stloc.0 //readEx
  IL_0007:  ldc.i4.7  //ارزش ذخیره شده در کد
  IL_0008:  stloc.1 //readConstValue
  IL_0009:  ldloc.0 //readEg
  IL_000a:  ldfld float64 [MyLib]MyLib.TestClass::piValue
  IL_000f:  stloc.2 //readReadOnlyValue
  IL_0010:  ret
} // end of method Program::Main
همانطور که می‌بینید ارزش ذخیره شده در کد IL، همان ارزشی است که در اسمبلی مجزا ذخیره شده است.
اگر در کتابخانه جانبی ارزش فیلد const را تغییر دهید و آن را مجدد کامپایل کنید، تا زمانیکه اسمبلی برنامه اصلی را کامپایل نکرده‌اید، همان ارزش قبلی در برنامه نمایش داده می‌شود.
برای غلبه بر این مشکل از فیلد‌های Static ReadOnly استفاده می‌کنیم.

مثال:
public class ReadonlyStatic
{
  public static readonly string x = "Hi";
  public static readonly string y;
  public ReadonlyStatic()
  {
     //y = "Hello"; This is wrong
  }
  static ReadonlyStatic()
  {
    y = "Hello";
  }
}

اولین مشکلی که با استفاده از فیلد‌های Static ReadOnly حل می‌شود، مشکل  Versioning فیلد‌های Const است. بدین ترتیب دیگر نیازی به کامپایل مجدد برنامه مصرف کننده نیست .
نکته بعدی که در کد فوق نشان داده شده‌است، فیلد‌های static readOnly در زمان تعریف و یا تنها از طریق سازنده‌ی static می‌توانند مقدار دهی شوند.

مقایسه ReadOnly و Static  :

ReadOnly
 Static
 هم در زمان تعریف و هم از طریق سازنده می‌توان آن را مقدار دهی کرد.   در زمان تعریف و تنها از طریق سازنده static می‌توان آن را مقدار دهی کرد.
مقدار بر اساس مقادیری که در سازنده‌ها تعیین می‌شود متفاوت است.
 مقادیر بعد از مقدار دهی اولیه تغییر نمی‌کنند. 


چه زمانی از Const و چه زمانی از ReadOnly استفاده کنیم :

  • زمانی باید از Const استفاده کرد که مطمئن هستیم ارزش ذخیره شده در آن در طول عمر یک برنامه تغییر نمی‌کند. بطور مثال ذخیره تعداد روز هفته در یک فیلد از نوع Constant. اگر شک داریم که ممکن است این ارزش تغییر کند، می‌توانیم از حالت static readOnly برای غلبه بر مشکل Versioning استفاده کنیم.
  • از آنجائیکه مقادیر constant در کد‌های IL گنجانده می‌شوند، برای رسیدن به کارآیی بهتر، مقادیری را که در طول عمر یک برنامه تغییر نمی‌کنند، به صورت  const تعریف می‌کنیم.
  • هر زمان تصمیم داشتیم Constant هایی به ازای هر وهله از کلاس داشته باشیم از ReadOnly استفاده می‌کنیم. 
 

‫ارتقاء به Angular 6: بررسی تغییرات RxJS

$
0
0
پس از ارتقاء Angular CLI و ساختار پروژه‌ی قبلی خود به نگارش 6، اولین موردی را که مشاهده خواهید کرد، این است: برنامه دیگر کامپایل نمی‌شود! اولین دلیل آن عدم استفاده‌ی از HttpClientمعرفی شده‌ی در نگارش 4.3 است و دومین دلیل مهم آن، تغییرات بنیادین RxJS است که خلاصه‌ی کاربردی آن‌را در این مطلب بررسی خواهیم کرد.


RxJS اکنون جزئی از پروژه‌های گوگل است

توسعه دهنده‌ی اصلی RxJS یا همان Ben Leshاکنون به گوگل پیوسته‌است و جزو تیم Angular است. بنابراین در آینده شاهد یکپارچگی بهتر این دو با هم خواهیم بود. البته RxJS هنوز هم به عنوان یک پروژه‌ی مستقل از Angular مدیریت خواهد شد.


آشنایی با تغییرات RxJS 5.5 جهت مهاجرت به RxJS 6.0 ضروری است

در مطلب «کاهش حجم قابل ملاحظه‌ی برنامه‌های Angular با استفاده از RxJS 5.5» با pipe-able operators آشنا شدیم و این موارد پایه‌های مهاجرت به RxJS 6.0 هستند. بنابراین پیش از مطالعه‌ی ادامه‌ی بحث نیاز است این مطلب را به خوبی مطالعه و بررسی کنید.


تغییر رفتار خطاهای مدیریت نشده در RxJS 6.0

تا پیش از RxJS 6.0 اگر خطای مدیریت نشده‌ای رخ می‌داد، این خطا به صورت synchronous به فراخوان صادر می‌شد. این رفتار در نگارش 6 تغییر کرده و صدور آن اینبار asynchronous شده‌است.
برای مثال یک چنین کدی تا پیش از RxJS 6.0 کار می‌کرد:
try {
    source$.subscribe(nextFn, undefined, completeFn);
} catch (err) {
    handleError(err);
}
از این جهت که دومین پارامتر در اینجا، متدی است که کار مدیریت خطا را انجام می‌دهد و چون ذکر نشده‌است، خطای رخ داده‌ی حاصل، به صورت همزمان به فراخوان صادر شده‌است و try/catch نیز در اینجا کار می‌کند. اما این مثال دیگر در نگارش 6 کار نخواهد کرد و صدور خطای مدیریت نشده، دیگر همزمان نیست و قسمت catch این قطعه کد دیگر هیچگاه فراخوانی نمی‌شود. البته این رفتار طبیعی است که می‌بایستی از نگارش‌های پیشین، با Observable هایی که عموما async هستند، وجود می‌داشت و اکنون اصلاح شده‌است.
برای اصلاح این کد در نگارش 6، همان پارامتر دوم متد را مقدار دهی کنید و try/catch را در صورت وجود حذف نمائید.


تغییرات مهم importها در RxJS 6.0

همانطور که در مطلب «کاهش حجم قابل ملاحظه‌ی برنامه‌های Angular با استفاده از RxJS 5.5» نیز بررسی کردیم، تا نگارش 5 این کتابخانه، importها به صورت زیر بودند:
 import 'rxjs/add/operator/map';
و پس از RxJS 5.5 امکان import آن‌ها با روش مخصوص ES 6 میسر شده‌است:
 import { map } from 'rxjs/operators';
هر چند نگارش 5.5 بهبودهای قابل ملاحظه‌ای و مزایایی مانند حذف ساده‌تر کدهای مرده، عدم تعریف عملگرها به صورت استاتیک و همچنین سازگاری بهتر با ابزارهایی مانند TSLint را به همراه دارد، اما باز هم دست آخر به تعداد زیادی import مانند کدهای زیر می‌رسیدیم:
import { timer } from 'rxjs/observable/timer';  
import { of } from 'rxjs/observable/of';
import { from } from 'rxjs/observable/from';
import { range } from 'rxjs/observable/range';
این مشکل در RxJS 6.0 برطرف شده‌است و در ماژول‌های مختلف برنامه حداکثر به دو سطر خلاصه شده‌ی زیر نیاز خواهیم داشت:
import { interval, of } from 'rxjs';  
import { filter, mergeMap, scan } from 'rxjs/operators';
در نگارش 6، تمام «نوع‌ها» مانند Observable و Subject و «متدهای ایجاد» مانند timer و interval از مسیر rxjs دریافت می‌شوند و تمام «عملگرها» مانند map و filter از مسیر rxjs/operators اضافه خواهند شد و ... همین!
البته RxJS 6.0 در کل به همراه 4 گروه کلی importها است که در زیر مشاهده می‌کنید (در اینجا مواردی که کمتر در برنامه‌های Angular به صورت مستقیم استفاده می‌شوند مانند ajax آن و یا webSocket هم قابل مشاهده هستند):
rxjs
rxjs/operators
rxjs/testing
rxjs/webSocket
rxjs/ajax


مواردی که از RxJS 6.0 حذف شده‌اند

برای کاهش حجم کتابخانه‌ی RxJS و همچنین جلوگیری از بکارگیری متدهایی که نمی‌بایستی خارج از کدهای اصلی خود RxJS استفاده شوند، تعداد زیادی از متدهای قدیمی آن و روش‌های کار پیشین با RxJS حذف شده‌اند. برای مثال شما در RxJS 5.5 می‌توانید برای کار با عملگر of، یا آن‌را از مسیر rxjs/add/observable/of دریافت کنید (همان روش وصله کردن تا پیش از RxJS 5.5) و یا آن‌را از مسیر rxjs/observable/of به روش مخصوص ES 6.0 به برنامه اضافه کنید و یا حتی امکان دریافت آن از مسیر rxjs/observable/fromArray نیز میسر است.
در RxJS 6.0 تمام این‌ها حذف شده‌اند و فقط روش زیر باقی مانده‌است:
 import { of } from 'rxjs';
به این ترتیب نه فقط حجم این کتابخانه کاهش یافته‌است، بلکه با کاهش سطح API آن، یادگیری آن نیز ساده‌تر شده‌است.


معرفی بسته‌ی rxjs-compat

در مطلب «ارتقاء به Angular 6: بررسی تغییرات Angular CLI» روش ارتقاء وابستگی‌های پروژه به نگارش 6 را بررسی کردیم. یکی از مراحل آن اجرای دستور زیر بود:
 ng update rxjs
این مورد صرفا وابستگی rxjs ذکر شده‌ی در فایل package.json را به آخرین نگارش آن به روز رسانی و همچنین نصب می‌کند.

پس از آن اگر پروژه را کامپایل کنید، پر خواهد بود از خطاهای rxjs، مانند:
 ERROR in node_modules/ng2-slim-loading-bar/src/slim-loading-bar.service.d.ts(1,10):
error TS2305: Module '"/node_modules/rxjs/Observable"' has no exported member 'Observable'.
این مشکل با بسته‌های ثالثی وجود دارند که هنوز از نگارش‌های قبلی RxJS استفاده می‌کنند و همانطور که عنوان شد، RxJS 6.0 شامل حذفیات و نقل و انتقالات بسیاری است. به همین جهت هیچکدام از کتابخانه‌های ثالث مبتنی بر نگارش‌های پیشین RxJS دیگر کار نکرده و کامپایل نخواهند شد.
برای رفع این مشکل و ارائه‌ی راه‌حلی کوتاه مدت، بسته‌ای به نام rxjs-compat ارائه شده‌است که سبب هدایت تعاریف قدیمی به تعاریف جدید می‌شود و به این ترتیب کدهای کتابخانه‌ی ثالث، بدون مشکل با نگارش 6 نیز قابل استفاده خواهند بود.
برای نصب آن نیاز است دستور زیر را صادر کنید:
 npm i rxjs-compat --save
اکنون اگر برنامه را مجددا کامپایل کنید، تمام خطاهای مرتبط با کتابخانه‌های ثالث مورد استفاده برطرف شده‌اند.

البته دقت داشته باشید از rxjs-compat به عنوان یک راه حل موقت باید استفاده کرد و نیاز است ابتدا کدهای خود را به روش pipe-able operators بازنویسی کنید و مسیرهای importها را اصلاح کنید و در آخر بسته‌های جدید وابستگی‌های ثالث را که از RxJS6 استفاده می‌کنند، نصب نمائید. در نهایت rxjs-compat را حذف کنید.


خودکار سازی اصلاح importها در برنامه‌های پیشین، جهت مهاجرت به RxJS 6.0

با توجه به این تغییرات و حذف و اضافه شدن‌ها در نگارش 6، تقریبا دیگر هیچکدام از importهای قبلی شما کار نمی‌کنند! و اصلاح آن‌ها نیاز به زمان زیادی خواهد داشت. به همین جهت تیم RxJS ابزاری را طراحی کرده‌اند که با اجرای آن بر روی پروژه، به صورت خودکار تمام importهای قبلی را به نگارش جدید تبدیل می‌کند. برای اینکار ابتدا ابزار rxjs-tslintرا نصب کنید:
 npm i -g rxjs-tslint
در ادامه فایل tslint.json پروژه خود را گشوده و مداخل زیر را به آن اضافه و ویرایش نمائید:
{
  "rulesDirectory": [
    "node_modules/rxjs-tslint"
  ],
  "rules": {
    "rxjs-collapse-imports": true,
    "rxjs-pipeable-operators-only": true,
    "rxjs-no-static-observable-methods": true,
    "rxjs-proper-imports": true
  }
}
سپس به ریشه‌ی پروژه‌ی خود وارد شده و دستور زیر را اجرا کنید:
 rxjs-5-to-6-migrate -p src/tsconfig.app.json
کیفیت کار این ابزار تا حدی است که تیم‌های داخلی گوگل از آن برای ارتقاء کدهای پیشین استفاده می‌کنند و بر روی پروژه‌های واقعی آزمایش شده‌است.
البته توصیه شده‌است این ابزار را بیش از یکبار نیاز است اجرا کنید.


خلاصه‌ی روش مهاجرت به RxJS 6x

ابتدا آخرین نگارش rxjs را نصب کنید:
 ng update rxjs
سپس rxjs-compat را جهت رفع کمبودهای کتابخانه‌های ثالث مورد استفاده نصب نمائید:
 npm i rxjs-compat --save
پس از آن ابزار rxjs-tslint را نصب و اجرا کنید:
npm i -g rxjs-tslint
rxjs-5-to-6-migrate -p src/tsconfig.app.json
و در آخر ارتقاء به روش pipe-ableرا باید مدنظر داشته باشید.


یافتن معادل‌های جدید دستورات قدیمی

در حین تبدیل کدهای قدیمی به جدید نیاز خواهید داشت تا معادل‌ها را بیابید. برای این منظور به مستندات رسمی این مهاجرت مراجعه کنید:
https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md
برای مثال در اینجا مشاهده خواهید کرد که معادل Observable.throw حذف شده، اکنون throwError است و همینطور برای مابقی.


یک مثال واقعی تغییر یافته

مخزن کد تمام مثال‌های سایت جاری که پیشتر منتشر شده‌اند، به نسخه‌ی 6 ارتقاء داده شد. ریز تغییرات RxJS 6.0 آن‌ها را در اینجامی‌توانید مشاهده کنید.

‫نمایش اشیاء موجود در View بر اساس دسترسی‌ها در ASP.NET MVC

$
0
0
سیستم دسترسی در یک سیستم، همیشه برای من چالش برانگیز بوده است. با دیدن کدهای مختلف از افراد مختلف، شیوه‌های گوناگونی از کدنویسی را دیده‌ام؛ ولی یکی از نکاتی که در بین آن‌ها بررسی نشده بود و یا از آن غافل مانده بودند، بررسی بعضی از عناصر موجود در ویو بود که باید با توجه به نقش کاربر سیستم، وضعیت آن بررسی میشد. 
برای مثال تصور کنید که شما دو کاربر دارید که هر دو سطح دسترسی به پروفایل کاربران دیگر را دارند. ولی یکی از کاربرها این توانایی را دارد تا با کلیک بر روی دکمه‌ای، لاگین کاربر مورد نظر را مسدود نماید، ولی دیگری نمی‌تواند این دکمه را ببیند، یا برای او به صورت غیرفعال نمایش داده شود. در اکثر این مواقع کاری که برنامه نویسان انجام میدهند، نوشتن توابع به شیوه‌های گوناگون در لابلای انبوهی از کدهای View هست تا بتوانند این مسئله را پیاده سازی کنند. ولی با اضافه شدن هر چه بیشتر این عناصر به صفحه و با توجه به انبوهی از ویووها و ویووکامپوننت‌ها کار بسیار سخت‌تر میشود.
 به همین جهت من از فیلترها در MVC کمک گرفتم و این موضوع را با توجه به خروجی نهایی صفحه بررسی میکنم. به این معنا که وقتی صفحه بدون هیچ گونه اعتبارسنجی در سطح ویو آماده شد، با استفاده از فیلتر مورد نظر که به صورت سراسری اضافه شده است، بررسی میکنم که آیا این کاربر حق دارد بعضی از المان‌ها را ببیند یا خیر؟ در صورتیکه برای هر المان موجود در صفحه اعتباری نداشته باشد، آن المان از صفحه حذف و یا غیرفعال میشود.
ابتدا یک صفحه را با المان‌های زیر میسازیم. از آنجا که بیشتر تگ‌های عملیاتی از نوع لینک و دکمه هستند، از هر کدام، سه عنصر به صفحه اضافه میکنیم:
<button data-perm="true" data-controller="c" data-action="r" data-method="post" data-type="disable">Test 1</button><button data-perm="true" data-controller="c" data-action="t" data-method="post">Test 2</button><button data-perm="false" data-controller="c" data-action="r" data-method="post">Test 3</button><a data-perm="true" data-controller="c" data-action="m" data-method="post">Test 4</a><a data-perm="false" data-controller="c" data-action="m" data-method="post">Test 5</a><a data-perm="true" data-controller="c" data-action="t" data-method="post">Test 6</a>
در اینجا ما از همان ساختار آشنای *-data استفاده میکنیم و معنی هر کدام از ویژگی‌ها برابر زیر است:
ویژگی
توضیحات
 perm   آیا نیاز به اعتبارسنجی دارد یا خیر؟ در صورتی که المانی مقدار Perm آن با مقدار true پر گردد، اعتبارسنجی روی آن اعمال خواهد شد.   
 controller   نام کنترلری که به آن دسترسی دارد.  
 action  نام اکشنی که به آن در کنترلر ذکر شده دسترسی دارد. 
 method   در صورتیکه دسترسی get و post و ... هر یک متفاوت باشد.  
 type نحوه برخورد با المان غیرمجاز. در صورتیکه با disable مقداردهی شود، المان غیرفعال و در غیر اینصورت، از روی صفحه حذف میشود.

سپس یک کلاس جدید ساخته و با ارث بری از ActionFilterAttribute، کار ساخت فیلتر را آغاز میکنیم:

    public class AuthorizePage: ActionFilterAttribute
    {
        private HtmlTextWriter _htmlTextWriter;
        private StringWriter _stringWriter;
        private StringBuilder _stringBuilder;
        private HttpWriter _output;
        IAuthorization _auth;
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {        
            _stringBuilder = new StringBuilder();
            _stringWriter = new StringWriter(_stringBuilder);
            _htmlTextWriter = new HtmlTextWriter(_stringWriter);
            _output = (HttpWriter) filterContext.RequestContext.HttpContext.Response.Output;
            filterContext.RequestContext.HttpContext.Response.Output = _htmlTextWriter;
            _auth = new Auth();
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            var response = _stringBuilder.ToString();

            response = AuthorizeTags(response);
            _output.Write(response);
        }

        public string AuthorizeTags(string response)
        {

            var doc = GetHtmlDocument(response);
            var nodes=doc.DocumentNode.SelectNodes("//*[@data-perm]");

            if (nodes == null)
                return response;

            foreach(var node in nodes)
            {
                var dataPermission = node.Attributes["data-perm"];
                if(!dataPermission.Value.TryBooleanParse())
                {
                    continue;
                }

                var controller = node.Attributes["data-controller"].Value;
                var action = node.Attributes["data-action"].Value;
                var method = node.Attributes["data-method"].Value;

                var access=_auth.Authorize(HttpContext.Current.User.Identity.Name , controller, action, method);

                if (access)
                    continue;

                var removeElm = true;
                var type = node.Attributes["data-type"]?.Value;
                if (type!=null && type.ToLower()== "disable")
                {
                    removeElm = false;
                }

                if(removeElm)
                {
                    node.Remove();
                    continue;
                }


                node.Attributes.Add("disabled", "true");
            }

            return doc.DocumentNode.OuterHtml;
        }

        private HtmlDocument GetHtmlDocument(string htmlContent)
        {
            var doc = new HtmlDocument
            {
                OptionOutputAsXml = true,
                OptionDefaultStreamEncoding = Encoding.UTF8
            };
            doc.LoadHtml(htmlContent);

            return doc;
        }

    }
در خطوط اولیه، از متد OnActionExecuting به عنوان یک سازنده برای پر کردن در لحظه هر درخواست استفاده میکنیم. htmlTextWriter که یک پیاده سازی از TextWriter هست، برای نوشتن بهینه کدهای HTML سودمند میباشد و از آن جهت که نیاز زیادی برای کار با رشته‌ها دارد، از StringBuilder در پشت صحنه استفاده می‌کند. جهت آشنایی بیشتر با این کلاس‌ها، مطلب StringBuilder و قسمت نظراتشرا مطالعه بفرمایید. همچنین یک httpWriter هم برای ایجاد خروجی در اکشن مورد نظر نیازمندیم و مورد آخر یک اینترفیس میباشد که جهت اعتبارسنجی مورد استفاده قرار میگیرد و این اینترفیس میتواند از طریق تزریق وابستگی‌ها پر شود:
  public interface IAuthorization
    {
        bool Authorize(string userId, string controller, string action, string method);
    }

ادامه کد در متد OnResultExecuted قرار دارد و متد اصلی کار ما میباشد. این متد بعد از صدور خروجی از اکشن، صدا زده شده اجرا میشود و شامل خروجی اکشن میباشد. خروجی اکشن را به متدی به نام AuthorizeResponse داده و با استفاده از بسته htmlagilitypackکه یک HTML Parser میباشد، کدهای HTML را تحلیل میکنیم. قاعده فیلترسازی المان‌ها در این کتابخانه بر اساس قواعد تعریف شده در XPath میباشد. بر اساس این قاعده ما گفتیم هر نوع تگی که دارای ویژگی data-perm میباشد، باید به عنوان گره‌های فیلتر شده برگشت داده شود. سپس مقادیر نام کنترلر و اکشن و ... از المان دریافت شده و با استفاده از اینترفیسی که ما اینجا تعریف کرده‌ایم، بررسی میکنیم که آیا این کاربر به این موارد دسترسی دارد یا خیر. در صورتیکه پاسخ برگشتی، از عدم اعتبار کاربر بگوید، گره مورد نظر حذف و یا در صورتیکه ویژگی data-type وجود داشته و مقدارش برابر disable باشد، آن المان غیرفعال خواهد شد. در نهایت کد تولیدی سند را به رشته تبدیل کرده و جایگزین خروجی فعلی میکنیم.
جهت تعریف سراسری آن در Global.asax داریم:
protected void Application_Start()
{
  GlobalFilters.Filters.Add(new AuthorizePage());
}

‫اسمبلی‌های دوست (Friend Assembly)

$
0
0
تعریف اصلاح کننده دسترسی داخلی (Internal Access Modifier)

این اصلاح کننده دسترسی باعث می‌شود عناصر یک اسمبلی تنها در سطح همان اسمبلی قابل دسترسی باشند. فرض کنید یک class library ای به نام MyLib با کلاس‌های Class1 و Class2 داریم. کلیه کلاس‌هایی که بصورت Internal تعریف شده‌اند، تنها می‌توانند توسط کلاس‌هایی که در MyLib هستند مورد استفاده قرار بگیرند. اگر در پروژه‌ای دیگر به کتابخانه‌ی MyLib ارجاعی بدهیم، کلیه کلاس‌های Internal آن غیر قابل دسترسی هستند. در سی شارپ بصورت پیش فرض کلیه کلاس‌ها بصورت Internal هستند.
مثال :
namespace MyLib
{
  internal class InternalClass
  {
  }
  public class PublicClass
  {
  }
}

استفاده از اسمبلی MyLib :
class Program
{
  static void Main(string[] args)
  {
      PublicClass _class = new PublicClass();//Correct
      InternalClass _s = new InternalClass();//Error
  }
}
همانطور که مشاهده می‌کنید استفاده از کلاس IntrenalClass، به‌علت مزین شدن آن به اصلاح کننده Internal، امکان پذیر نیست.

حال اگر بخواهیم به اعضای Internal خارج از اسمبلی آنها دسترسی داشته باشیم راه حل چیست ؟

فرض کنید کتابخانه‌ای را برای مدیریت محصولات تعریف کرده‌ایم و می‌خواهیم یک مصرف کننده بتواند به یکی از اعضای Internal کتابخانه ما دسترسی داشته باشد (تنها به یکی از کلاس‌ها و نه کلیه کلاس‌های کتابخانه). اولین راه حلی که به ذهن ما می‌رسد، تبدیل کلاس از حالت Internal به Public است. اما این کار باعث می‌شود این کلاس در معرض استفاده همه مصرف کننده‌ها قرار گیرد و مهمتر از آن شاید مشکل امنیتی ایجاد شود. در این شرایط مفهوم اسمبلی‌های دوست (Friend assembly) به کمک ما می‌آید.
با استفاده از این قابلیت ما به کلاس و یا dll خود می‌گوییم که این کلاس، دوست توست؛ می‌توانی کلیه اعضای داخلی خود را با آن به اشتراک بگذاری.
برای انجام این کار کافی است فضای نام مورد نظر خود را با خصوصیت اسمبلی زیر مزین کنیم:
 [assembly: InternalsVisibleToAttribute("FriendAssembly")]
همانطور که مشاهده می‌کنید نام اسمبلی مقصد که قرار است از اعضای Internal استفاده کند، بصورت رشته معرفی می‌شود.
این خصوصیت در فضای نام زیر قرار دارد :
 System.Runtime.CompilerServices;

اگر اسمبلی ما جزو اسمبلی‌های strong name (نام قوی) باشد، باید کلید عمومی 160 بایتی را نیز بطور کامل قید کنید.
برای استخراج کلید عمومی از اسمبلی‌های نام قوی می‌توانید از عبارت پرس جوی LINQ زیر استفاده کنید:
string key = string.Join ("",
Assembly.GetExecutingAssembly().GetName().GetPublicKey()
.Select (b => b.ToString ("x2")));
معرفی کلید عمومی در اسمبلی‌های دارای نام قوی :
 [assembly: InternalsVisibleTo ("StrongFriend, PublicKey=0024f000048c...")]

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

$
0
0
یکی از مواردی که با Angular CLI 6.0به شدت ساده شده‌است، ایجاد پروژه‌های «کتابخانه» Angular است. برای مثال شاید در حین استفاده‌ی از بعضی از کتابخانه‌ی ثالث تهیه شده‌ی برای Angular با خطای ذیل مواجه شده باشید:
Please open an issue in the library repository to alert its author and ask them to 
package the library using the Angular Package Format (https://goo.gl/jB3GVv).
این خطا زمانی رخ می‌دهد که تهیه کننده‌ی کتابخانه، فرمت بسته‌های Angular را رعایت نکرده باشد و ... رعایت کردن آن نیز کار بسیار مشکلی است. نگارش 6 در پشت صحنه، پروژه‌ی موفق ng-packagr را به مجموعه‌ی CLI اضافه کرده‌است و از این پس توسط خود CLI می‌توان کتابخانه‌های استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانه‌ها‌ی npm حاصل را نیز به همراه دارد. مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار با بسته‌های فعلی npm مخصوص Angular وجود دارند؛ مانند خطایی که عنوان شد. برای مثال بدون استفاده‌ی از این ابزار، نیاز است مستندات چند صفحه‌ای ساخت کتابخانه‌های Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0 به صورت خودکار ایجاد و مدیریت می‌شود.


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

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


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


فایل جدید public_api.ts

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


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

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


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

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

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

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


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

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


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

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


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

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

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

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

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

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


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

برای اینکه این کتابخانه‌ی تولیدی را در اختیار عموم، در سایت npm قرار دهیم، ابتدا باید کتابخانه را در حالت production build تولید و سپس آن‌را publish کرد:
ng build my-lib --prod
cd dist/my-lib
npm publish
سطر اول، کتابخانه‌ی my-lib را در حالت production تواید می‌کند. سپس به پوشه‌ی فایل‌های نهایی تولید شده وارد می‌شویم و دستور npm publish را صادر می‌کنیم.
البته دستور آخر نیاز به ایجاد یک اکانت در سایت npm و وارد شدن به آن‌را دارد. جزئیات بیشتر آن در اینجا.

‫کار با Visual Studio در ASP.NET Core

$
0
0
پیش نویس: این مقاله ترجمه شده فصل 6 کتاب Pro Asp.Net Core MVC2می‌باشد.

کار با Visual Studio

در این مقاله، یکسری توضیحاتی در مورد ویژگی‌های کلیدی ویژوال استودیو به برنامه نویس‌های (توسعه دهنده‌های) پروژه‌های Asp.net Core MVC ارائه می‌دهیم.

 
ایجاد یک پروژه

در ابتدا یک پروژه‌ی وب جدید Asp.net core را به نام Working و بر اساس قالب Empty ایجاد می‌کنیم. سپس در کلاس startup، قابلیت MVC را فعال میکنیم (کدهای این قسمت، در فصل 5کامل شرح داده شده‌است)
 

namespace Working
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}

ایجاد مدل:

یک پوشه جدید را به نام Models ایجاد می‌کنیم و بعد در این پوشه یک کلاس جدید را به نام Product ایجاد می‌کنیم و کدهای زیر را در کلاس ایجاد شده قرار میدهیم (این قسمت در فصل 5 نیز شرح داده شده‌است):
namespace Working.Models
{
    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}
برای ایجاد یک فروشگاه ساده از اشیاء محصول، من یک فایل کلاس را به نام SimpleRepository.cs به پوشه Models اضافه و از آن برای تعریف کلاس استفاده کردم.
 

namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
کلاس Stores، اشیا مدل را در حافظه ذخیره می‌کند. یعنی هر تغییری را که در Model داده باشید، زمانیکه نرم افزار متوقف یا Restart شود، از بین می‌رود. یک فروشگاه ناپیوسته برای مثال در این فصل کافی است. اما این رویکردی نیست که در بسیاری از پروژه‌های واقعی استفاده شود. در فصل 8 یک مثال را پیاده سازی می‌کنیم تا اطلاعات مدل Store را به صورت مداوم در بانک اطلاعاتی ذخیره کند.

نکته:من یک مشخصه (Property) استاتیک را به نام SharedRepository تعریف کردم که دسترسی به SimpleRepository را فراهم می‌کند و می‌تواند در طول برنامه از آن استفاده شود. این بهترین کار نیست، ولی میخواهم یک مشکل رایج را که در توسعه MVC روبرو میشوید، نشان دهم. من راه بهتری را برای کار با اجزای مشترک، در فصل 18 توضیح می‌دهم.


ایجاد Controller و View

در پوشه Controllers، یک فایل جدید را به نام HomeController.cs ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);
    }
}
این یکی Action Method ایی به نام Index است که اطلاعات مدل را دریافت می‌کند و برای View پیشفرض، جهت نمایش ارسال می‌کند. برای ایجاد View هم بر روی پوشه Views/Home راست کلیک کرده و یک View را به نام index.cshtml ایجاد کنید؛ با کدهای زیر:
@{
    Layout = null;
}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>>Working with Visual Studio</title></head><body><table><thead><tr><td>Name</td><td>Price</td></tr></thead><tbody>            @foreach (var p in Model)
    {<tr><td>@p.Name</td><td>@p.Price</td></tr>}</tbody></table></body></html>
این View شامل یک جدول است که از حلقه foreach Razor، برای ایجاد ردیف‌هایی برای هر شیء مدل استفاده می‌کند. جایی که هر ردیف، حاوی سلول‌هایی برای خواص نام و قیمت است. اگر شما برنامه کاربردی را اجرا کنید، نتایج حاصل را در شکل خواهید دید:

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

دو نوع مختلف از بسته‌های نرم افزاری مورد نیاز برای Asp.Net Core MVC وجود دارند.

معرفی NuGet 

ویژوال استودیو به همراه یک ابزار گرافیکی برای مدیریت بسته‌های NET. است که در یک پروژه گنجانده شده‌است. برای باز کردن این ابزار، گزینه Management NuGet Packages for Solution را از منوی Tools ➤ NuGet Package Manager انتخاب کنید. به این ترتیب ابزار NuGet باز می‌شود و لیستی از بسته‌هایی که قبلا نصب شده‌اند، نمایش داده می‌شود؛ همانطور که در شکل زیر نشان داده شده‌است:
 


برگه‌ی Installed، خلاصه‌ای از بسته‌هایی را که قبلا در پروژه نصب شده‌اند، نشان می‌دهد. از برگه‌ی Browse، برای یافتن و نصب بسته‌های جدید می‌توان استفاده کرد و برگه‌ی Updates، فهرست package هایی را که نسخه‌های اخیر آن‌ها منتشر شده‌اند، نمایش می‌دهد.

 
معرفی بسته‌ی MICROSOFT.ASPNETCORE.ALL

اگر شما از نسخه‌های قبلی Asp.Net Core استفاده کرده باشید، باید یک لیست طولانی از بسته‌های NuGet را به پروژه جدید خود اضافه نمایید. Asp.Net Core2 یک بسته‌ی متفاوت را به نام Microsoft.AspNetCore.All معرفی می‌کند.

پکیچ Microsoft.AspNetCore.All یک meta-package است که شامل تمام بسته‌های Nuget مورد نیاز Asp.net Core و MVC Framework است. یعنی شما دیگر نیازی به نصب تک به تک این نوع بسته‌ها ندارید و هنگامیکه برنامه خود را منتشر می‌کنید، هر بسته‌ای از بسته‌های Meta-package که مورداستفاده قرار نمی‌گیرند، حذف خواهند شد. البته این بسته در نگارش 2.1، قسمت All آن به App تغییر نام یافته‌است.
 
معرفی بسته‌های Nuget و موقعیت ذخیره سازی آن‌ها

ابزار NuGet لیست بسته‌های پروژه را در فایل projectname.csproj نگهداری می‌کند. در اینجا <projectname> با نام پروژه جایگزین میشود. برای مثال در پروژه فوق اطلاعات Nuget، در فایل WorkingWithVisualStudio.csproj ذخیره می‌شوند. ویژوال استودیو محتویات فایل csproj را در پنجره‌ی Solution Explorer نمایش نمی‌دهد. برای ویرایش این فایل، روی پروژه در پنجره‌ی Solution Explorer راست کلیک کنید و گزینه‌ی Edit WorkWithVisualStudio.csproj را از منوی باز شده، انتخاب کنید. ویژوال استودیو فایل را برای ویرایش باز می‌کند. فایل csproj یک فایل XML است و شما در آن عنصری را مانند قطعه کد زیر در آن می‌بینید که Asp.net Core Meta package را به پروژه اضافه می‌کند:
<ItemGroup><PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /></ItemGroup>
در اینجا یک بسته با نام و شماره نسخه مورد نیاز مشخص شده‌است. اگرچه بسته Meta-Package شامل تمام ویژگی‌های مورنیاز Asp.Net Core MVC می‌باشد، اما شما هنوز هم باید بسته‌های دیگری را به پروژه اضافه کنید تا بتوانید از ویژگی‌های اضافی خاص آن‌ها استفاده کنید. این بسته‌ها را می‌توان توسط رابط‌های خط فرمان و یا ابزار گرافیکی آن اضافه کرد. حتی شما می‌توانید فایل Csproj را به صورت مستقیم ویرایش کنید و ویژوال استودیو میتواند تغییرات بسته‌ها را شناسایی کرده، دانلود و نصب کند.
 


هنگامیکه از NuGet برای اضافه کردن یک بسته به پروژه‌ی خود استفاده می‌کنید، به صورت خودکار به همراه هر بسته‌ای که به آن وابستگی دارد، نصب می‌شود. شما می‌توانید بسته‌های Nuget و وابستگی‌های آن‌ها را در SolutionExpolrer از طریق گزینه‌ی Dependencies -> Nuget مشاهده کنید که هر یک از بسته‌های موجود در فایل csproj و وابستگی‌های آن‌ها را نشان می‌دهد. برای نمونه بسته Meta-Package ASP.Net Core دارای تعداد زیادی وابستگی است؛ برخی از آنها در شکل زیر دیده میشوند:


 
معرفی Bower

یک بسته Client-Side، شامل محتوایی است که به مشتری ارسال می‌شود؛ مانند فایل‌های جاوا اسکریپت، Css Stylesheets و یا تصاویر. از Nuget برای مدیریت این نوع فایل‌ها در پروژه نیز استفاده میشود. اما اکنون Asp.Net Core MVC پشتیبانی توکاری را از یک ابزار مدیریت بسته‌های سمت کاربر، به نام Bower نیز ارائه می‌دهد. Bower یک ابزار منبع باز ( Open Source ) است که در خارج از مایکروسافت و دنیای NET. توسعه داده شده و نگهداری می‌شود.

نکته: Bower به تازگی منسوخ شده اعلام گردیده‌است. ممکن است هشدارهایی را که ابزارهای جایگزین را پیشنهاد می‌کنند نیز مشاهده کنید. با این حال پشتیبانی از Bower با ویژوال استودیو یکپارچه شده‌است و در نگارش 2.1 ابزار مدیریت سمت کلاینت جدید دیگری را نیز بجای آن معرفی کرده‌اند.
 

معرفی لیست بسته‌های Bower

بسته‌های Bower از طریق فایل ویژه‌ی bower.json مشخص می‌شوند. برای ایجاد این فایل در پنجره Solution Explorer روی پروژه WorkingWithVisualStudio راست کلیک کنید و Add -> New Item را از منوی باز شده انتخاب کنید. سپس قالب مورد نظر Bower Configuration File را از Asp.net Core -> Web -> General  Category انتخاب نمائید؛ مانند تصویر زیر:
 


ویژوال استودیو نام bower.json را برای آن قرار میدهد. برروی ok کلیک می‌کنیم و یک فایل جدید، با محتویات پیشفرض زیر به پروژه اضافه میشود:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {}
}
به علاوه برای فایل Bower.json، تصویر زیر بسته‌های Client Side وابسته به Bower را نشان میدهد. از این قسمت برای اضافه کردن وابستگی‌های برنامه نیز استفاده میشود.


نکته:منبع بسته‌های Bower در لینک http://bower.io/searchوجود دارد. شما می‌توانید بسته‌ها مورنظر را در اینجا جستجو و به پروژه اضافه کنید.

بعد از اینکه بسته‌ها نصب شدند، محتویات فایل bower.json به صورت زیر می‌باشد:
{
  "name": "asp.net",
  "private": true,
  "resolutions": {
    "jquery": "3.3.1"
  }
}

در ادامه بسته Bootstrap CSS به پروژه اضافه شده‌است. زمانیکه شما فایل Bower.json را ویرایش می‌کنید، ویژوال استادیو لیستی از نام بسته‌ها و نسخه‌های بسته‌های موجود را نمایش می‌دهد؛ مانند تصویر زیر:


در زمان نوشتن این مطلب، آخرین نسخه‌ی پایدار بسته بوت استرپ، 3،3،7 است. البته اگر در دقت کنید، در اینجا سه گزینه‌ی ارائه شده‌ی توسط ویژوال استودیو وجود دارند: 3.3.7 و 3.3.7^ و 3.3.7~. شماره نسخه می‌تواند در طیف وسیعی از روش‌های مختلف در فایل bower.json مشخص شود. مفیدترین آنها در جدول زیر شرح داده شده‌اند. استفاده از شماره نسخه صریح یک بسته، امن‌ترین راه برای مشخص کردن یک بسته است. این تضمین می‌کند که شما همیشه با همان نسخه کار می‌کنید؛ مگر اینکه عمدا فایل bower.json را برای پاسخ گویی به درخواست‌های دیگری به روز رسانی کنید:
  فرمت   توضیحات 
  3.3.7 بیان شماره مستقیم بسته نصب شده و تطبیق دقیق آن با شمار نسخه ، e.g ، 3.3.7 
  * با استفاده از یک ستاره به Bower اجازه نصب آخرین نسخه را می‌دهد
3.3.7 =<3.3.7<
پیشوند یک شماره نسخه با < یا =< به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای که بزرگتر یا بزرگتر مساوی آن نسخه‌ی معین است، نصب شود 
3.3.7 =>3.3.7
پیشوند یک شماره نسخه با > یا => به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای را که کوچکتر یا کوچکتر و مساوی نسخه‌ی معین است، نصب شود 
  3.3.7~ پیشوند یک شماره نسخه با یک tilde (با کاراکتر ~ ) به نسخه‌هایی که دو شماره اولیه آن‌ها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره آخر آن نسخه متفاوت باشد. مانند نسخه‌های 3.3.9 و 3.3.8 و اجازه نصب نسخه 3.4.0 را نمیدهد؛ چون شماره دوم آن متفاوت است.
  3.3.7^ پیشوند یک شماره نسخه با یک قلم (کاراکتر ^) به نسخه‌هایی که شماره اول آنها مشابه باشند، اجازه نصب می‌دهد؛ حتی اگر شماره دوم آن‌ها متفاوت باشد. مانند نسخه‌های 3.3.1 و 3.4.1 و 3.5.1 اما نسخه 4.0.0 اجازه نصب ندارد 
 
نکته: برای مثال در این کتاب، من فایل bower.json را مستقیما ایجاد و ویرایش می‌کنم. ویرایش این فایل ساده است و به شما کمک می‌کند تا اطمینان حاصل کنید که نتایج مورد انتظار را در صورت پیگیری به همراه داشته باشد. همچنین ویژوال استودیو ابزار گرافیکی را نیز برای مدیریت بسته‌های bower فراهم می‌کند. شما می‌توانید با کلیک راست بر روی فایل bower.json و انتخاب Manage Bower packages به منوی باز شده دسترسی داشته باشید. ویژوال استادیو فایلهای bower.json را برای تغییرات نظارت می‌کند و به صورت خودکار از ابزار Bower برای دانلود و نصب بسته‌ها استفاده می‌کند. هنگامیکه شما تغییرات فایل را ذخیره می‌کنید، ویژوال استودیو بسته‌ی BootStrap را دانلود می‌کند و در پوشه‌ی wwwroot/lib ذخیره می‌کند.


مانند Nuget نیز Bower وابستگی‌های مرتبط با بسته‌های اضافه شده‌ی به یک پروژه را مدیریت می‌کند. BootStrap برای دسترسی به برخی از ویژگی‌های پیشرفته، به JQuery که یک کتابخانه‌ی جاوا اسکریپتی است، تکیه می‌کند. به همین دلیل است که دو بسته را در شکل فوق نشان داده است. شما می‌توانید لیست بسته‌ها و وابستگی‌های آنها را به صورت باز شده در بخش مورد نظر در Solution Explorer مشاهده کنید.

به روزرسانی بسته Bootstrap

در ادامه کتاب، من از نسخه قبلی Bootstrap CSS framework استفاده می‌کنم. هنگامی که دارم این را می‌نویسم، تیم Bootstrap در حال توسعه‌ی نسخه‌ی 4 bootStrap است و چندین بار منتشر شده‌است. این نسخه‌ها به عنوان "آلفا" برچسب گذاری شده‌اند، اما کیفیت آن‌ها بالا است و برای استفاده در نمونه‌های این کتاب به اندازه کافی پایدار است. با توجه به انتخاب نوشتن این کتاب با استفاده از Bootstrap 3 و نسخه پیش از نسخه بوت استرپ 4 و به زودی بایگانی شدن آن، تصمیم گرفتم از نسخه جدید استفاده کنم؛ حتی اگر برخی از نام‌های کلاس‌ها که برای شیوه نامه‌های عناصر HTML استفاده می‌شوند، احتمالا قبل از انتشار نهایی تغییر یابند. این مورد به این معنا است که شما باید همان نسخه از Bootstrap را که برای گرفتن نتایج موردنظر از خروجی نیاز دارید، استفاده  کنید.

برای به روزرسانی بسته Bootstrap، شماره نسخه را در فایل bower.json تغییر دهید. مانند کد زیر:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "4.0.0-alpha.6"
  }
}
زمانی که شما تغییرات فایل bower.json را ذخیره می‌کنید، ویژوال استودیو نسخه جدید BootStrap را دانلود می‌کند.
معرفی توسعه و کامپایل مداوم

توسعه نرم افزار وب اغلب می‌تواند یک فرآیند تکراری باشد، جایی که تغییرات کوچکی را به ویووها یا کلاس‌ها می‌دهید و برنامه را اجرا می‌کنید تا اثرات آن را آزمایش کنید. MVC و ویژوال استودیو همکاری می‌کنند تا از این رویکرد مداوم استفاده کنند تا تغییرات را سریع‌تر و آسان‌تر ببینید.

 اعمال تغییرات در Razor Views  
در زمان توسعه، تغییراتی که به Razor View اعمال میشوند، به محض رسیدن درخواست‌های HTTP، از مرورگر دریافت میشوند. برای اینکه ببینید چطور کار می‌کند، برنامه را با انتخاب گزینه Start Debugging از منوی Debug اجرا کنید و هنگامیکه یک برگه‌ی مرورگر باز شد و اطلاعات نمایش داده شد، تغییراتی را که در زیر نمایش میدهم در فایل Index.cshtml اعمال کنید.

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>>Working with Visual Studio</title></head><body><h3>Products</h3><table><thead><tr><td>Name</td><td>Price</td></tr></thead><tbody>
            @foreach (var p in Model)
            {<tr><td>@p.Name</td><td>@($"{p.Price:C2}")</td></tr>}</tbody></table></body></html>
تغییرات را در فایل Index ذخیره می‌کنیم و صفحه وب را با استفاده از دکمه browser Reload مجددا بارگذاری می‌کنیم. تغییرات در View (یک عنوان و فرمت را برای مشخصه Price به عنوان ارز وارد کردیم) در مرورگر هم اعمال شده است؛ مانند تصویر زیر:



اعمال تغییرات در کلاس‌های #C

برای کلاس‌های #C، از جمله کنترلرها و مدل‌ها، دو رویکرد موجود را که از طریق آیتم‌های مختلف در منوی Debug انتخاب می‌شوند، شرح می‌دهم:

Start Without Debugging
تغییرات در کلاس‌ها در پروژه به صورت خودکار زمانیکه یک درخواست HTTP دریافت می‌شود، برای مشاهده‌ی یک تجربه‌ی توسعه‌ی پویا، کامپایل می‌شوند. در این حالت برنامه بدون امکانات دیباگ و اشکال‌زادیی اجرا می‌شود.

Start Debugging
به شما اجزا میدهد صریح تغییرات را کامپایل کنید و برنامه را اجرا کنید ، بررسی مشکلات هم در زمان اجرا پروژه انجام میگیرد.به شما اجرا بررسی و تجزیه و تحلیل هر گونه مشکل در کد را میدهد.

 
کامپایل خودکار کلاس ها

در طول توسعه عادی، این چرخه کامپایل سریع به شما اجازه می‌دهد تا فورا تاثیر تغییرات خود را ببینید؛ حالا می‌تواند این تغییر اضافه نمودن یک اکشن جدید و یا ویرایش نمایش اطلاعات یک Model باشد. برای ارائه‌ی این نوع از توسعه، ویژوال استودیو به محض رسیدن درخواست HTTP از مرورگر، تغییرات را دریافت و کلاس‌ها را به صورت خودکار کامپایل می‌کند. برای دیدن اینکه چگونه کار می‌کند، گزینه Start Without Debugging را از منوی Debug در ویژوال استودیو انتخاب کنید. هنگامیکه مرورگر داده‌های برنامه را نمایش می‌دهد، تغییرات زیر را در فایل Home controller ایجاد کنید:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products
            .Where(p => p.Price < 50));
    }
}
در این تغییرات با استفاده از LINQ محصولات را فیلتر می‌کنیم به طوری که فقط کالاهایی را که price آنها کمتر از 50 است، نمایش داده می‌شوند. تغییرات را در فایل کلاس controller ذخیره کنید و پنجره مرورگر را دوباره باز کنید. بدون توقف و یا راه اندازی مجدد برنامه در ویژوال استادیو، درخواست HTTP از مرورگر باعث عملیات کامپایل میشود و برنامه با استفاده از تغییرات کلاس Controller، دوباره راه اندازی خواهد شد و نتیجه را در زیر میتوانید ببینیدکه محصولات Kayak را از جدول حذف می‌کند.

ویژگی کامپایل خودکار زمانی مفید است که همه چیز برنامه ریزی شود. مشکل این است که خطاهای کامپایلر، در زمان اجرا و در مرورگر بجای ویژوال استودیو نمایش داده می‌شوند. در این حالت زمانیکه یک مشکل وجود دارد، سخت می‌توان متوجه شد که چه مشکلی ایجاد شده است. برای مثال، کدهای زیر اضافه کردن یک مقدار Null را به مجموعه نمایش میدهد. 
namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
            products.Add("Error", null);
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
مشکلی مانند ورودی Null تا زمانیکه برنامه اجرا نشود، نمایش داده نمیشود. بارگذاری صفحه مرورگر باعث می‌شود کلاس SimpleRepository به صورت خودکار کامپایل شود و برنامه دوباره راه اندازی خواهد شد. هنگامیکه MVC نمونه‌ای از کلاس Controller را برای پردازش درخواست HTTP از مرورگر ایجاد می‌کند، سازنده HomeController کلاس SimpleRepository را ایجاد خواهد کرد که به نوبه خود سعی می‌کند مقدار Null اضافه شده در لیست را پردازش کند. مقدار Null باعث بروز یک مشکل می‌شود، اما مشخص نیست مشکل چیست. مرورگر یک پیام مفید را نمایش نمی‌دهد.
توانایی نمایش صفحات خطاها 
زمانیکه مشکلی در پنجره‌ی مرورگر ایجاد شد، می‌توان یک راهنمای با اطلاعات مفید را نمایش داد. این قابلیت را می‌توانید با فعال کردن نمایش صفحات انجام داد که باید در تنظیمات کلاس Startup تغییرات زیر را اعمال کنید.

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDeveloperExceptionPage();
            }
        }
    }
}
اگر پنجره مرورگر را دوباره بارگذاری کنید، فرآیند کامپایل خودکار به صورت خودکار برنامه را بازسازی می‌کند و یک پیام خطای مفید‌تری را در مرورگر ایجاد می‌کند. مانند تصویر زیر:

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

 
استفاده از Debugger

ویژوال استادیو از اجرای یک برنامه MVC با استفاده از Debugger نیز پشتیبانی می‌کند که اجازه می‌دهد برنامه برای بررسی وضعیت نرم افزار و دنبال کردن درخواستی که به برنامه ارسال میشود، متوقف و از این طریق، پیگیری شود. این مورد نیاز به یک سبک متفاوت از توسعه را دارد. زیرا تغییراتی را در کلاس‌های #C میدهیم، تا زمانیکه برنامه مجددا راه اندازی نشود، اعمال نمی‌شوند ( هرچند تغییرات Razor View هنوز هم به صورت خودکار اعمال میشوند). این سبک توسعه به همراه استفاده‌ی از ویژگی کامپایل خودکار نیست؛ اما Debugger ویژوال استودیو عالی است و می‌تواند بینش عمیق‌تری را در مورد نحوه‌ی کارکرد برنامه داشته باشد. برای اجرای برنامه با استفاده Debugger، در ویژوال استودیو از منوی Debug گزینه‌ی Start Debugging را انتخاب کنید. ویژوال استودیو کلاسهای #C در پروژه را قبل از اجرای برنامه کامپایل می‌کند. اما شما همچنان می‌توانید با استفاده از موارد موجود در منوی Build، کد خود را به صورت دستی نیز کامپایل کنید.

مثال فوق حاوی مقدار NULL است که سبب می‌شود یک NullReferenceException توسط کلاس SimpleRepository پرتاب شود. این حالت برنامه را قطع و کنترل اجرا را به توسعه دهنده منتقل می‌کند؛ همانطور که در شکل زیر نشان داده شده است



نکته:اگر Debugger خطا را نفهمد، گزینه‌ی Windows ➤ exception settings را از منوی Debugger ویژوال استودیو انتخاب کنید و اطمینان حاصل کنید که تمام انواع خطاهای در لیست خطاهای زمان اجرای زبان مشترک، تایید شده‌است.
تنظیم یک Break-point

Debugger عامل اصلی خطا را نمایش نمی‌دهد؛ تنها مکان آن‌را آشکار می‌کند. عبارتیکه ویژوال استودیو برجسته می‌کند نشان می‌دهد که این مشکل زمانی رخ می‌دهد که فیلتر کردن اشیاء با استفاده از LINQ انجام شود، اما یک کار کوچک لازم است تا از جزئیات کاسته شود و به علت اصلی برسد.
Breakpoint عبارتی است که به Debugger می‌گوید تا برنامه را متوقف کند و کنترل دستی برنامه را به برنامه نویس میدهد. شما می‌توانید وضعیت برنامه را بازبینی کنید و ببینید چه اتفاقی می‌افتد و به صورت اختیاری روند کاری را دوباره ادامه دهید.
برای ایجاد Breakpoint، روی عبارت راست کلیک کنید و در منوی باز شده، گزینه Breakpoint -> Insert Breakpoint را انتخاب کنید.

به عنوان مثال: یک Breakpoint به خط کد AddProduct در کلاس SimpleRepository اعمال کنید. همانطور که در شکل زیر نمایش داده میشود:
 


برنامه را اجرا کنید؛ با استفاده از Debug -> Start Debugging و یا با استفاده از Debug -> Restart برنامه را Restart می‌کنیم. در طی درخواست اولیه HTTP، برنامه اجرا میشود تا به نقطه‌ای که Break Point دارد برسد و در آنجا برنامه متوقف میشود. در این نقطه، شما می‌توانید از آیتم‌های منوی Debug ویژوال استودیو یا کنترل‌ها در بالای پنجره، برای کنترل اجرای برنامه استفاده کنید؛ یا از نمایش‌های مختلف Debugger موجود از طریق Debug -> Windows برای بررسی وضعیت برنامه استفاده می‌کنیم.
مشاهده مقادیر داده در ویرایشگر کد
رایج‌ترین استفاده Break Point، ردیابی مشکلات در کد شماست. قبل از اینکه بتوانید یک مشکل را رفع کنید، باید بدانید چه اتفاقی در حال رخ دادن است و یکی از ویژگیهای مفید ویژوال استودیو این است که توانایی مشاهده و کنترل ارزش متغیرها را درست در ویرایشگر کد، میدهد.
اگر اشاره‌گر ماوس را بر روی پارامتر p به متد AddProduct که توسط Debugger برجسته شده‌است، حرکت دهید، یک فرم ظاهر خواهد شد که ارزش فعلی p را نشان می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است. من یک نمونه بزرگ شده از محتویات فرم ظاهر شده را نمایش میدهم تا به راحتی بتوانید متن در آن را بخوانید.
 


این مورد ممکن است مؤثر به نظر نرسد، چون شیء داده در یک سازنده همانند BreakPoint تعریف شده‌است. اما این ویژگی‌ها برای هر متغیری کار می‌کند. شما می‌توانید مقادیر را مشاهده کنید تا مقادیر خود و فیلد آنها را ببینید. هر مقدار دارای یک دکمه پین​​ کوچک به سمت راست است. برای زمانیکه کد در حال اجراست، برای نظارت بر مقدار، از آن استفاده کنید.
اشاره‌گر ماوس را بر روی متغیر P قرار دهید و مرجع محصول را پین کنید. مرجع پیوست شده را باز کنید تا بتوانید نام و قیمت را نیز ببینید؛ مانند شکل زیر:
 


گزینه Continue را از منوی Debug در ویژوال استادیو انتخاب کنید تا برنامه ادامه پیدا کند. از آنجا که در برنامه حلقه Foreach وجود دارد، برنامه که دوباره اجرا میشود، وقتی مجددا به BreakPoint رسید، برنامه متوقف میشود. مقادیر پین شده در شکل زیر نشان میدهند که چگونه متغیر P و خواص آن تغییر می‌کنند.
 


استفاده از پنجره متغیرهای محلی ( Local Windows )

یکی از ویژگی‌های مرتبط، پنجره Locals است که با انتخاب گزینه‌ی منوی Debug ➤ Windows ➤ Locals باز می‌شود. پنجره‌ی Locals، مقدار متغیرها را به شکلی مشابه پنل پین شده نمایش می‌دهد، اما در اینجا تمام اشیاء محلی را نسبت به Break Point نمایش می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است:
 


هربار که Continue را انتخاب می‌کنید، اجرای برنامه ادامه یافته و یک شیء دیگر توسط حلقه foreach پردازش می‌شود.
اگر ادامه دهید، در زمان ویرایش کد، در هر دو پنجره Locals و در مقادیر پنل پین شده، شما مرجع Null را می‌بینید. برای کنترل اجرای برنامه، می‌توانید جریان را از طریق کد خود در دیباگر دنبال کنید و احساس کنید که چه اتفاقی می‌افتد.

برای غیرفعال کردن BreakPoint، روی  عبارت راست کلیک کنید و از منوی باز شده گزینه Delete BreakPoint را انتخاب کنید. برنامه را دوباره راه اندازی کنید و جدول داده ساده‌ای را که در شکل نشان داده شده، مشاهده خواهید کرد.


 
استفاده از Browser Link

ویژگی Browser Link می‌تواند روند توسعه را با قرار دادن یک یا چند مرورگر تحت کنترل ویژوال استودیو، ساده سازی کند. این ویژگی مخصوصا مفید است اگر شما نیاز به دیدن اثر تغییرات را در طیف وسیعی از مرورگرها دارید. قابلیت Browser Link با و یا بدون Debugger کار می‌کند و به این معنا است که می‌توانیم هر فایلی را در پروژه تغییر دهیم و تاثیر تغییر را بدون نیاز به تغییری در مرورگر مشاهده کنیم.

 
راه اندازی BrowserLink

برای فعال کردن Browser Link باید در کلاس Startup، تنظیمات را تغییر دهید. مانند کد زیر:

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


استفاده از Browser Link

برای درک اینکه Browser Link چگونه کار می‌کند، در ویژوال استودیو گزینه Start Without Debugging را از منوی Debug انتخاب می‌کنیم. ویژوال استودیو برنامه را اجرا می‌کند و یک برگه جدید مرورگر را برای نمایش نتیجه باز می‌کند. با بازبینی HTML ارسال شده به مرورگر، شما خواهید دید که حاوی بخش دیگری مانند این است:
 

<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>>Working with Visual Studio</title></head><body><h3>Products</h3><table><thead><tr><td>Name</td><td>Price</td></tr></thead><tbody><tr><td>Lifejacket</td><td>&#xA3;48.95</td></tr><tr><td>Soccer ball</td><td>&#xA3;19.50</td></tr><tr><td>Corner flag</td><td>&#xA3;34.95</td></tr></tbody></table><!-- Visual Studio Browser Link --><script type="application/json" id="__browserLink_initializationData">
        {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false}</script><script type="text/javascript" src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink" async="async"></script> <!-- End Browser Link --></body></html>
نکته:اگر قسمت اضافی را نمی‌بینید، لینک مرورگر را از منوی نشان داده شده‌ی در شکل زیر فعال کنید و مرورگر را دوباره بارگذاری کنید.


ویژوال استادیو یک جفت عناصر اسکریپت را به HTML فرستاده شده‌ی به مرورگر اضافه می‌کند که برای بازکردن یک اتصال طولانی مدت HTTP با سرور برنامه کاربردی است؛ تا زمانیکه ویژوال استودیو مجددا برنامه را ری‌استارت کند. کد زیر تغییر در فایل Index و تاثیر استفاده از Browser Link را نشان میدهد.
 

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>>Working with Visual Studio</title></head><body><h3>Products</h3><p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p><table><thead><tr><td>Name</td><td>Price</td></tr></thead><tbody>
            @foreach (var p in Model)
            {<tr><td>@p.Name</td><td>@($"{p.Price:C2}")</td></tr>}</tbody></table></body></html>
تغییر در فایل View را ذخیره کنید و Refresh Linked Browsers را از منوی Browser Link در نوار ابزار ویژوال استودیو انتخاب کنید؛ همانطور که در شکل نشان داده شده است.  اگر Browser Link کار نمی‌کند، بارگیری مجدد مرورگر یا راه اندازی مجدد ویژوال استادیو را امتحان کنید).
 


کد جاوا اسکریپتی که در HTML ارسال شده به مرورگر جاسازی شده، صحفه را دوباره بارگذاری می‌کند؛ برای دیدن تاثیرات کد اضافه شده که اضافه کردن  یک timestamp ساده است.
 
نکته: عناصر اسکریپت Browser Link فقط در پاسخ‌های موفق جاسازی شده است. به این معنا که اگر یک خطا هنگام کامپایل در هنگام اجرا کردن یک Razor View یا مدیریت یک درخواست ایجاد شود، اتصال بین مرورگر و ویژوال استودیو از بین میرود و شما بعد از حل مشکل باید صفحه را مجدد بارگذاری کنید.

 
استفاده از مرورگرهای متعدد

Browser Link می‌تواند برای نمایش یک برنامه در مرورگرهای متعددی به طور همزمان استفاده شود و می‌تواند زمانی مفید باشد که شما می‌خواهید تفاوت‌های پیاده سازی را بین مرورگرهای مختلف کنترل کنید و یا ببینید که چگونه یک برنامه بر روی ترکیبی از مرورگرهای دسکتاپ و تلفن همراه ارائه می‌شود.
برای انتخاب مرورگرهایی که استفاده می‌شوند، مرورگر را با استفاده از دکمه IIS Express در نوار ابزار ویژوال استودیو، انتخاب کنید؛ همانطور که در شکل زیر نشان داده شده است.
 


ویژوال استودیو لیستی از مرورگرهایی را که در مورد آنها اطلاعاتی دارد، نمایش میدهد. در عکس زیر مرورگرهایی را که من در سیستم خود نصب کرده‌ام، نشان می‌دهد. برخی از آنها با ویندوز مانند Internet Explorer و Edge نصب می‌شوند.

 
ویژوال استادیو معمولا مرورگرهای رایجی را که نصب میشوند، نمایش میدهد. اما شما می‌توانید با استفاده از دکمه‌ی Add، برای اضافه کردن مرورگری که به صورت خودکار لیست نشده نیز استفاده کنید. همچنین می‌توانید ابزار تست شخص ثالث مانند Browser Stack را نیز راه اندازی کنید که مرورگرها را بر روی سرویس‌های ابری میزبان ( cloud-hosted ) و ماشین‌های مجازی اجرا می‌کند.

من سه مرورگر را در شکل انتخاب کردم: Chrome ، Internet Explorer و Edge. با کلیک بر روی دکمه Browse، فعالیت هر سه مرورگر شروع می‌شود و باعث می‌شود URL مثال برنامه را بارگذاری کند؛ همانطور که در شکل نشان داده شده است.
 


با استفاده از منوی Browser Link Dashboard، شما می‌توانید ببینید که چه مرورگرهایی در Browser Link انتخاب شده‌اند. داشبورد آن نشانی اینترنتی نمایش داده شده توسط هر مرورگر را نشان می‌دهد و در اینجا هر مرورگر را می‌توان به صورت جداگانه رفرش کرد.
 


آماده سازی جاوا اسکریپت و CSS برای استقرار

هنگامی که Client-Side بخشی از یک برنامه وب را ایجاد می‌کنید، معمولا تعدادی از فایل‌های جاوا اسکریپت و CSS سفارشی را تهیه می‌کنید که برای تکمیل آن‌ها، از بسته‌های نصب شده‌ی توسط Bower استفاده می‌شود. این فایل‌ها نیاز به پردازش دارند تا آنها را برای تحویل در یک محیط تولید، بهینه سازی کنند تا تعداد درخواستهای HTTP و میزان پهنای باند شبکه مورد نیاز برای ارسال آنها به مشتری، به حداقل برسد. این فرآیند به عنوان بسته بندی شناخته می‌شود.
 

فعال کردن تحویل محتوای استاتیک

ASP.Net Core شامل پشتیبانی از ارائه فایل‌های استاتیک از پوشه wwwroot به مشتریان است. اما این امکان به صورت پیشفرض در زمان ایجاد یک پروژه‌ی خالی جدید فعال نیست و شما باید با قرار دادن عبارتی در فایل StartUp آن را فعال کنید؛ مانند کد زیر:
 

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseStaticFiles();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


اضافه کردن محتوای استاتیک به پروژه

برای نشان دادن فرآیند بسته بندی، من نیاز به اضافه کردن تعدادی محتوای استاتیک به پروژه و یکی کردن آن‌ها با برنامه‌ی نمونه را دارم. برای این منظور ابتدا یک پوشه‌ی جدید را به نام wwwroot/css ایجاد کنید که محل متداولی برای فایل‌های سفارشی CSS است. من فایلی را به نام First.css با استفاده از قالب آیتم Style Sheet اضافه کردم؛ همانطور که در شکل زیر نشان داده شده است. قالب Style Sheet در مسیر Asp.Net Core -> Web -> Content Section وجود دارد.
 


فایل First.Css را ویرایش کنید و محتوای زیر را در آن قرار دهید.
h3 {
}

table, td {
    border: 2px solid black;
    border-collapse: collapse;
    padding: 5px;
}
من این روند را تکرار کردم و یک فایل دیگر را نیز به نام second.css در پوشه wwwroot/css ایجاد کردم.

فایل‌های جاوا اسکریپت معمولا در پوشه wwwroot/js قرار میگیرند. من این پوشه را ایجاد کردم. فایل‌های جاوا اسکریپت را می‌توانید در مسیر Asp.Net Core -> Web -> Script انتخاب کنید. همانطور که در شکل زیر نشان داده شده است.


من کد جاوا اسکریپتی ساده زیر را به این فایل جدید اضافه کردم؛ همانطور که در لیست نشان داده شده است.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the third.js file";
    document.querySelector("body").appendChild(element);
});

من به بیش از یک فایل جاوا اسکریپت نیاز دارم. بنابراین فایل دیگری را به نام fourth.js نیز در پوشه wwwroot ایجاد می‌کنم و محتوای زیر را در آن قرار میدهم.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the fourth.js file";
    document.querySelector("body").appendChild(element);
});


به روز رسانی View

گام نهایی، به روز رسانی فایل Index.cshtml برای استفاده از Css و فایل جاوا اسکریپت است. کد‌های آن در زیر نشان داده شده است:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>>Working with Visual Studio</title><link rel="stylesheet" href="css/first.css" /><link rel="stylesheet" href="css/second.css" /><script src="js/third.js"></script><script src="js/fourth.js"></script></head><body><h3>Products</h3><p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p><table><thead><tr><td>Name</td><td>Price</td></tr></thead><tbody>
            @foreach (var p in Model)
            {<tr><td>@p.Name</td><td>@($"{p.Price:C2}")</td></tr>}</tbody></table></body></html>
اگر برنامه کاربردی را اجرا کنید، محتویات نشان داده شده‌ی در شکل زیر را مشاهده خواهید کرد. محتوای موجود توسط شیوه نامه‌های CSS شبیه سازی شده است و کد جاوا اسکریپتی جدیدی را اضافه کرده است.


یکی کردن فایل‌های سمت کلاینت در برنامه‌های MVC

در حال حاضر چهار فایل استاتیک وجود دارند و مرورگر باید چهار درخواست را برای دریافت فایل‌های استاتیک ایجاد کند و هر یک از این فایل‌ها نیازمند پهنای باند بیشتری است که باید به مشتری تحویل داده شود؛ زیرا آنها حاوی فضای سفید و نام متغیرها هستند که برای توسعه دهنده‌ها معنا دار هستند؛ اما برای مرورگرها اهمیتی ندارند.
ترکیب فایل‌هایی هم نوع، تلفیق نامیده می‌شود و در آن کار ساختن فایل‌ها به صورتی کوچکتر انجام می‌شود. هر دوی این کارها در برنامه Asp.Net Core MVC توسط  Bundler & Minifier مخصوص ویژوال استودیو انجام میشود.


نصب افزونه‌های ویژوال استودیو

اولین قدم برای نصب افزونه، انتخاب از منوی Tools -> Extensions and Update و کلیک بر روی مجموعه Online است تا افزونه‌های ویژوال استودیو را در مجموعه نمایش بدهد. نام افزونه را در جعبه جستجوی در گوشه‌ی سمت راست بالای پنجره وارد کنید؛ همانطور که در شکل زیر نشان داده شده است. محل نصب افزونه را مشخص می‌کنیم و بر روی دانلود کلیک می‌کنیم تا آن را به ویژوال استودیو اضافه کند. ویژوال استودیو را مجدد راه اندازی کنید تا فرآیند نصب تکمیل شود.


دسته بندی و یکی کردن فایل‌ها

پس از نصب افزونه، ویژوال استودیو را مجددا راه اندازی کنید و پروژه نمونه را باز کنید. با افزودن افزونه، می‌توانید چندین فایل هم نوع را در Solution Explorer انتخاب کنید. آنها را با یکدیگر ترکیب کرده و محتویات آنها را کوچکتر کنید. به عنوان مثال فایل‌های First.css و Second.css را در Solution Explorer را انتخاب و کلیک راست کرده و سپس Bundler & Minifier -> Bundle and Minify Files را از منوی باز شده انتخاب کنید . همانطور که در شکل زیر نشان داده شده است.
 


فایل خروجی را با عنوان bundle.css ذخیره کنید. در Solution Explorer یک بسته جدید ایجاد میشود. اگر شما این فایل را باز کنید، خواهید دید که محتویات هر دو فایل CSS جداگانه ترکیب شده‌اند و تمام فضای سفید آن‌ها حذف شده‌است. البته شما نمی‌خواهید به طور مستقیم با این فایل کار کنید؛ اما این فایل کوچکتر است و فقط یک اتصال HTTP را برای ارائه CSS styles به مشتری نیاز دارد.

مراحل قبل را برای فایل‌های third.js و fourth.js تکرار کنید تا فایل‌های جدید bundle.js و bundle.min.js در پوشه wwwroot ایجاد شوند.

احتیاط:اطمینان حاصل کنید که فایل‌ها را به ترتیبی که توسط مرورگر بارگیری می‌شوند، انتخاب کنید تا ترتیب دستورات Style‌ها یا دستورات کد را در فایل‌های خروجی حفظ کنید. به عنوان مثال دقت کنید که فایل third.js قبل از فایل fourth.js انتخاب شده باشد تا مطمئن باشید دستورات به ترتیب و به درستی اجرا می‌شوند.
کد زیر، عناصر پیوند فایل‌های جداگانه‌ای را که باید در فایل Index.cshtml قرار گیرند، نمایش میدهد:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>>Working with Visual Studio</title><link rel="stylesheet" href="css/bundle.min.css" /><script src="js/bundle.min.js"></script></head><body><h3>Products</h3><p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p><table><thead><tr><td>Name</td><td>Price</td></tr></thead><tbody>
            @foreach (var p in Model)
            {<tr><td>@p.Name</td><td>@($"{p.Price:C2}")</td></tr>}</tbody></table></body></html>
اگر برنامه را اجرا کنید، هیچ تغییر بصری وجود نخواهد داشت؛ اما فایلهای آن یکی شده‌اند و با حجم کمتر و با تعداد اتصالات کمتری از سرور دریافت می‌شوند.

همان زمان که عملیات جمع آوری و یکی کردن را انجام می‌دهید، رکورد عملیات انجام شده را در فایلی به نام bundleconfig.json در پوشه‌ی wwwroot پروژه نگهداری می‌کند. در اینجا یک نمونه از فایل تولیدی را مشاهده می‌کنید:
[
  {
    "outputFileName": "Views/wwwroot/css/bundle.css",
    "inputFiles": [
      "Views/wwwroot/css/First.css",
      "Views/wwwroot/css/second.css"
    ]
  },
  {
    "outputFileName": "Views/wwwroot/js/bundle.js",
    "inputFiles": [
      "Views/wwwroot/js/fourth.js",
      "Views/wwwroot/js/third.js"
    ]
  }
]
 

خلاصه
در این بخش من توضیحاتی را در مورد ویژگی‌هایی که ویژوال استودیو برای طراحی برنامه‌های وب به توسعه دهنده‌ها ارائه میدهد، شرح دادم که شامل کامپایل خودکار کلاس‌ها، Browser Link و یکی کردن فایل‌های سمت کلاینت ( bundling and minification ) بود. 

‫اجزاء معماری سیستم عامل اندروید :: بخش اول

$
0
0
اجزای معماری اندروید به صورت کلی به 4 دسته اصلی تقسیم می‌شود که عبارتند از:
  1. Kernel
  2. ماشین مجازی Dalvik
  3. application framework 
  4. applications
برای درک بهتر این معماری و اجزای درونی آن، به تصاویر زیر توجه کنید:


هسته اندروید (kernel) چیست؟

اندروید بر روی هسته لینوکس نسخه 2.6 به بالاتر اجرا می‌شود. هسته، اولین و نخستین لایه‌ی نرم افزاری در اندروید است که با سخت افزار به صورت کاملا مستقیم در تعامل می‌باشد. تغییرات در هسته و یا ساختن دوباره‌ی آن چیزی نیست که لازم باشد از ابتدا به ساکن و مجددا توسط یک برنامه نویس انجام شود! فقط سازندگان سخت افزار قادر به تغییرات درون هسته هستند و این یک امر غیرطبیعی و ناممکن برای برنامه نویسان خواهد بود؛ چرا که یک برنامه نویس تنها قادر است تا لایه‌های نرم افزاری را تغییر دهد نه سخت افزار آن را و این مورد تنها به‌عهده‌ی افرادی است که با سخت افزار اندروید در ارتباط هستند.


کتابخانه ها

اجزای کتابخانه‌ها بعنوان یک لایه مترجم بین هسته و فریم ورک‌ها عمل می‌کنند. این کتابخانه‌ها و اجزای درونی آنها با زبان C و ++C نوشته شده‌اند؛ اما از طریق یک API در زبان جاوا در اختیار توسعه دهندگان قرار می‌گیرند تا به سهولت قابل استفاده باشند. برنامه نویسان و توسعه دهندگان می‌توانند از طریق فریم ورک‌های زبان جاوا برای دسترسی مستقیم به کتابخانه‌های اصلی C و ++C استفاده کنند.

برخی از کتابخانه‌های اصلی عبارتند از:

  1. LibWebCore 
  2. Media libraries 
  3. Graphics libraries 
ماشین مجازی Dalvik زمان اجرای اجزای کتابخانه‌ها با آنها در تعامل می‌باشد تا باعث یکپارچگی اجرای آنها باشد. ماشین مجازی، بخش مهم و ضروری در اندروید است تا برنامه‌های سیستمی و شخص ثالث را بدرستی اجرا نماید.

ماشین مجازی Dalvik چیست؟

در اصل آقای Dan Bornstein ماشین مجازی Dalvik را نوشت. ماشین مجازی دال‌ویک برای اجرای برنامه‌ها بر روی دستگاه‌های مختلف با منابع بسیار محدودنوشته شده بود و این یکی از اولویت‌های این ماشین بود تا بتواند با منابع محدود بر روی دستگاه در تعامل باشد و برنامه را اجرا نماید. به طور معمول ماشین دالویک برای تلفن‌های همراه‌ای استفاده می‌شد که توان پردازشی پایین، عمر باتری کم و همچنین حافظه‌ی اندکی داشتند. ماشین مجازی دال‌ویک فایل‌های با پسوند dex.را اجرا می‌کند. این فایل‌ها با استفاده از کلاس‌های جاوا کامپایل شده‌اند که با در نظر گرفتن نوع کلاس class. یا jar. انجام می‌شوند و فایل‌های class. را به یک ثابت (constant) مشترک و هماهنگ تبدیل می‌نمایند. ابزار DX، که در Android SDK موجود است، این تبدیل را انجام می‌دهد.

به تصویر زیر توجه کنید:


به تصویر زیر توجه کنید؛ پس از تبدیل، فایل‌های dex.به طرز قابل توجهی کوچکتر و کم حجم‌تر می‌شوند!


Application Framework چیست؟

اپلیکیشن فریم ورک، یکی از بلاک‌های مهم ساختن سیستم نهایی می‌باشد. این فریم ورک‌ها مجموعه‌ای از خدمات را درون سیستم برای برنامه‌نویس یا توسعه دهندگان جهت نوشتن یک برنامه بدون مشکل فراهم می‌کنند. به‌عنوان یک توسعه دهنده شما کد را می‌نویسید و فقط از API‌ها برای تکمیل آنها در زبان جاوا استفاده می‌کنید. از طریق یک API در زبان جاوا قادر خواهید بود تا به هسته و کتابخانه‌های C و ++C دسترسی داشته باشید.


بعنوان نمونه : کد زیر به شما نحوه پخش یک فایل ویدئویی را نشان می‌دهد

package com.example.android.apis.media;

import com.example.android.apis.R;
import android.app.Activity;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;
public class VideoViewDemo extends Activity {
 /**
  * TODO: Set the path variable to a streaming video URL or a local media
  * file path.
  */
 private String path = "";
 private VideoView mVideoView;

 @Override
 public void onCreate(Bundle icicle) {
  super.onCreate(icicle);
  setContentView(R.layout.videoview);
  mVideoView = (VideoView) findViewById(R.id.surface_view);

  if (path == "") {
   // Tell the user to provide a media file URL/path.
   Toast.makeText(
    VideoViewDemo.this,
    "Please edit VideoViewDemo Activity, and set path" + " variable to your media file URL/path",
    Toast.LENGTH_LONG).show();
  } else {
   /*
    * Alternatively,for streaming media you can use
    * mVideoView.setVideoURI(Uri.parse(URLstring));
    */
   mVideoView.setVideoPath(path);
   mVideoView.setMediaController(new MediaController(this));
   mVideoView.requestFocus();
  }
 }
}



‫معرفی و روش استفاده از Dispatcher در WPF

$
0
0
باید این سوال را از خودمان بپرسیم که اصلا چه نیازی به استفاده از Dispatcher در WPF می‌باشد و این که ما چه نیازی داریم با ساختمان Thread آشنا شویم؟

می‌دانید که در پروژه و نرم افزارهایی که توسعه داده می‌شوند بعضی مواقع مشاهده می‌کنیم قسمتی از برنامه نیاز به زمان یا پردازش بیشتری دارد تا عملیات خود را به اتمام برساند و رابط کاربری (User Interface) برنامه در این حین منتظر می‌ماند و یا به اصطلاح (Freeze) می‌شود تا یک پردازش طولانی به اتمام برسد و بعد رابط کاربری به کار خود ادامه می‌دهد و بعضی مواقع پنجره Windows explorer is not responding را مشاهده کرده‌اید که با کلیک بر روی Close the program از آن گذر می‌کنیم. در حالی که برنامه ما باید حالت Responsive  داشته باشد و نباید برنامه با یک‌چنین مواردی روبه رو شود.

ساختمان Thread :
تمامی اشیاء (Objects) در این مدل به دو گروه تقسیم بندی می‌شوند:
Single-Threaded Apartment
Multi-Threaded Apartment
ساختمان  Single-Threaded Apartment (STA) :

STA شامل فقط یک Thread می‌باشد و تمامی اشیاء در این ساختمان فقط می‌توانند متدی را که در این Thread صدا زده می‌شود دریافت کنند؛ یا به عبارتی دیگر هنگامیکه شیء به Thread ای متصل می‌شود دیگر شما قادر نخواهید بود اشیاء UI را به صورت مستقیم و یا از طریق Thread‌های دیگر تغییر دهید. لازم به ذکر است WPF از مدل STA پشتیبانی می‌کند که شامل نکات  زیر می‌باشد:
1. یک Thread در کل برنامه اجرا می‌شود و شامل همه Object‌های WPF می‌باشد.
2. عناصر یا المان‌های  WPF، قابلیت تنظیم Thread Affinity را دارند. منظور این می‌باشدکه Thread‌ها نمی‌توانند با یکدیگر ارتباط  برقرار کنند.
3. اشیاء WPF که قابلیت Thread Affinityرا دارند، از یک Object Dispatcher مشتق می‌شوند.
4. اشیاء WPF متعلق به Thread ای می‌باشند که توسط آن ایجاد شده است و Thread دیگر قادر به دسترسی مستقیم به این اشیاء نمی‌باشد.

ساختمان Multi-Threaded Apartment (MTA) :

MTA  شامل یک یا چندین Thread می‌باشد. تمامی اشیاء در این مدل می‌توانند از هر Thread ای فراخوانی شوند. در حقیقت رجیستر‌های CPU و Ram که در اختیار برنامه قرار داده شده تکه تکه می‌شوند و برنامه ما تعداد Thread‌های مورد نظر را اجرا می‌کند و یکی از راه حل‌های بر طرف کردن اینکه رابط کاربری ما در حالت انتظار باقی نماند استفاده از پردازش کارها بصورت غیر همزمان (Asynchronous) می‌باشد که در اصطلاح Multithreading نامیده می‌شود. 

WPF Dispatcher :

WPF از مدل STA پشتیبانی می‌کند. زمانیکه برنامه WPF  اجرا می‌شود، به طور خودکار یک Dispatcher Object ساخته می‌شود و متد Run صدا زده می‌شود و از آن برای آماده سازی صف پیام‌ها استفاده می‌شود که مدیریت یک صف از کارها بر عهده آن است و کارهای UI را در یک صف FIFO اجرا می‌کند. WPF یک Dispatcher را برای UI Thread ایجاد می‌کند. بنابراین شما نمی‌توانید یک dispatcher دیگر برای آن تعریف کنید. 

نکته : دیاگرام زیر نمایش می‌هد که تمامی اشیاء WPF از DispatcherObjectمشتق شده‌اند.

نکته : زمانیکه برنامه WPF  اجرا می‌شود دو Thread ساخته می‌شود:
1.  UI Thread (Main Thread) 
2.  Render Thread
 

UI Thread : مسئولیت تمامی ورودی‌های کاربر، handle events, paints screen و اجرای کدهای برنامه را بر عهده دارد.

Render Thread : در Background اجرا می‌شود و برای Render صفحه نمایش WPF استفاده می‌شود.

نکته: همانطور که آشنا شدیم WPF نمی‌تواند UI Thread را از طریق یک Thread دیگر به روز رسانی کند و یا به عبارتی دیگر یک Thread نمی‌تواند بصورت مستقیم به اشیایی که توسط Thread دیگر ایجاد شده، دسترسی داشته باشد.

Dispatcher برای این کار دو متد را در اختیار ما قرار می‌دهد :

متد Invoke : یک Action یا Delegate را میگیرد و متد آن‌را به صورت همزمان اجرا می‌کند. این مورد به این معنا است که تا زمانی که اجرای متد کامل نگردد، عملیاتی صورت نخواهد گرفت و یا به عبارتی دیگر فراخوان را تا زمانیکه زمانبندی به پایان برسد، در حالت مسدود نگهداری خواهد کرد.

مثال :

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Task.Factory.StartNew(() =>
            {
                InvokeMethodExample();
            });
    }
    private void InvokeMethodExample()
    {
        Thread.Sleep(2000);
        Dispatcher.Invoke(() =>
            {
                btn1.Content = "By Invoke";
            });
    }
}
متد BeginInvoke : یک Delegate را می‌گیرد و متد آن‌را به صورت ناهمزمان اجرا می‌کند. این مورد به این معنا است که قبل از آنکه متد فراخوانی گردد، برگشت داده می‌شود و یا به عبارتی دیگر به ما اجازه می‌دهد تا Thread جاری را با کنترل دوباره به جریان بیندازیم.
مثال :
public MainWindow()
{
    InitializeComponent();
    Task.Factory.StartNew(() =>
        {
            BeginInvokeExample();
        });
}
private void BeginInvokeExample()
{
    DispatcherOperation op = Dispatcher.BeginInvoke((Action)(() => {
         btn1.Content = "By BeginInvoke";
        }));
}
BeginInvoke یک شیء DispatcherOperation برگشت می‌دهد. این شیء یا Object برای دانستن اینکه وضعیت عملیات کامل شده است یا خیر می‌تواند استفاده شود و همچین دو رویداد Aborted و Completed را فراهم می‌کند. 

‫اجزاء معماری سیستم عامل اندروید :: بخش دوم

$
0
0
در مطلب قبلی در مورد سه ویژگی اصلیمعماری اندروید توضیحاتی ارائه شد و در این مطلب ویژگی آخر از این معماری را توضیح خواهم داد:

Applications در معماری اندروید چه کاربردی دارد؟
اجزای یک اپلیکیشن در پلتفرم اندروید جزء اصلی ارائه به کاربر نهایی می‌باشد؛ بدین معنا که کاربر تنها با برنامه در ارتباط است و سیستم عامل، میزبان آن برنامه یا اپلیکیشن خواهد بود. اپلیکیشن جاییست که لیست تماس‌ها، شماره تلفن‌ها، پیام‌های کاربر و ... در آنجا قرار می‌گیرند و با دیگر اجزای نرم افزاری در ارتباط هستند!
بعنوان یک توسعه دهنده اندروید، محصول نهایی، در قالب یک اپلیکیشن با استفاده از API‌ها و کتابخانه‌ها و همچنین ماشین مجازی دال‌ویک اجرا می‌شوند. اگر شما بتوانید تغییراتی را در سیستم عامل اصلاح و ویرایش کنید، تنها در سطوح لایه‌های نرم افزاری اعمال میشود و شما بر روی امنیت برنامه یا اپلیکیشن درون هسته دسترسی لازم را ندارید و این یک معضل است و باید در لایه‌های اولیه برنامه، امنیت را بر روی برنامه اعمال کنید.
با این وجود اگر هسته یا دستگاه آسیب ببیند و مورد سوءاستفاده قرار بگیرد کاری از دست شما برنمی آید!


Security به معنای ایمنی یا امنیت در اندروید به چه معناست؟
ایمنی یا امنیت در اندروید، موضوع بسیار وسیعی است که در ابعاد مختلف این پلتفرم قابل بحث است. اول اجازه دهید هویت شما را تشخیص دهیم. آیا شما یک توسعه دهنده هستید؟ یا شاید شما یک کاربر عادی هستید که به حفاظت از خودتان از یک حمله اینترنتی علاقمندید! در هر صورت، شما در حال نوشتن یک برنامه هستید که به وسیله یک نفر دیگر و یا احتمالا هزاران نفر در هزاران مایل دورتر نصب خواهد شد.


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


خطرات امنیتی (Security Risks) 
کاربران دستگاه‌های تلفن همراه در مقایسه با کاربران دسکتاپ، با برخی مخاطرات منحصر به فردی مواجه هستند که نباید نادیده گرفته شود. صرف‌نظر از امکان از دست دادن تجهیزات سخت افزاری، اطلاعات، حریم خصوصی و داده‌های محرمانه شخصی کاربران دستگاه تلفن همراه را به خطر می‌اندازند. چرا این موضوع در پلتفرم جامع اندروید تا این حد پر اهمیت است؟ آیا شما بعنوان توسعه دهنده به این نکات دقت داشته‌اید؟
اول اینکه، کیفیت داده‌های ذخیره‌شده در دستگاه‌های تلفن همراه کاربر بیشتر شخصی می‌شود تا مواردی دیگر! به غیر از ایمیل، پیام‌های فوری، SMS / MMS ، لاگ تماس‌ها، عکس‌ها و پست صوتی وجود دارند که عموما توسعه دهندگان را دچار مشکل می‌کند. 

برخی از گزینه‌های فوق بر روی یک کامپیوتر رومیزی هم وجود دارند، ولی اهمیت این داده‌ها بر روی اندروید و اجزای آن اهمیت فوق العاده‌ای دارد. اطلاعات روی دستگاه موبایل شما به احتمال زیاد از ارزش بیشتری برخوردار خواهد بود، چرا که آن‌ها را در یک صفحه 4 - 5 اینچی به همراه خود حمل می‌کنید و با خود هر کجا می‌برید! این حالت، یک پلتفرم همگرارا بوجود می‌آورد؛ به این دلیل که سیستم رومیزی شما و تلفن همراه یک مجموعه غنی و کامل از اطلاعات حساس هستند که هردوی آنها شامل اطلاعات شخصی می‌باشند و برای شما اهمیت زیادی خواهند داشت. تصور کنید زمانیکه برای جلوگیری از نفوذ یا به سرقت رفتن شماره تلفن‌های خود، یک پشتیبان بر روی سیستم رو میزی خود تهیه می‌کنید و فایل پشتیبان شماره‌های تماس را بر روی سیستم شخصی نگه داری می‌کنید! آیا این همان پلتفرم همگرا نیست؟ آیا این دو سیستم مکمل هم نیستند؟حتی اگر همگام‌سازی را با یک مکان دوردست (Google Drive) انجام دهید، با این حال شما فقط در مقابل از دست دادن داده‌ها محافظت کرده‌اید و نه از دست دادن حریم خصوصی! 

همچنین در نظر بگیرید که فرمت داده‌های ذخیره‌شده در دستگاه‌های تلفن همراه، تعیین و مشخص شوند! این کار اطلاعات حساس شما را به مرز سرقت نزدیکتر می‌کند. هر تلفن همراه SMS / MMS ، تماس‌ها، و پست صوتی خواهد داشت. مکان‌های ذخیره شده از روی GPS و مواردی دیگر که قطعا اطلاع دارید، تمامی اینها جزء مواردی هستند که خطرات امنیتی را در سیستم عامل اندروید شامل می‌شود. حالا در نظر بگیرید که این اطلاعات تا چه حد مهم است؟ برای کاربرانی که هیچ گونه پشتیبانی از اطلاعاتی از خود ندارند، از دست دادن داده‌ها قابل تصور نیست!

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

در مطلب بعدی پیرامون امنیت معماری اندروید صبحت خواهیم کرد...

‫اجزاء معماری سیستم عامل اندروید (قسمت اول معماری امنیتی اندروید) :: بخش سوم

$
0
0
همانطور که در مطالب قبلی توضیح داده شد، اندروید بر روی هسته‌ی لینوکس نگارش 2.6 به بالا اجرا می‌شود. در این مطلب قصد داریم تا نگاهی به معماری امنیتی اندروید داشته باشیم.
Privilege Separation به چه معناست؟
هسته‌ی اندروید یک مدل جداسازی امتیازی (Privilege separation) را در زمان اجرای برنامه‌های کاربردی اجرا می‌کند. این مورد به این معنا است که مانند سیستم یونیکس، سیستم‌عامل اندروید به یک برنامه نیاز دارد تا تنها با شناسه‌ی کاربر خود و شناسه‌ی گروه خاص خودش اجرا شود. بخش‌های معماری سیستم اندروید به این روش از هم جدا شده‌اند. Privilege Separation یک ویژگی امنیتی مهم است؛ زیرا رایج‌ترین انواع حملات را انکار می‌کند و در نتیجه یکی از اولویت‌های یک برنامه می‌باشد. از آنجایی که عملیات جداسازی امتیازی را هسته انجام می‌دهد، لذا این مورد یکی از مباحث پر اهمیت در طراحی هسته می‌باشد که به شدت مورد توجه خواهد بود. تنها فلسفه پشت این طرح این است که اطمینان حاصل شود، هیچ برنامه‌ی کاربردی نمی‌تواند به کد یا داده‌های دیگر برنامه‌ها و یا به دستگاه کاربر و یا خود سیستم نفوذ کند. یک برنامه ممکن است مستقیما از فهرست تماس دستگاه یا تقویم خوانده نشود! این ویژگی همچنین به عنوان sandboxing شناخته می‌شود.
Permissions چگونه کار می‌کند؟
اجازه دهید با یک مثال ساده توضیح دهم: ما برنامه‌ای را داریم که صدا را ضبط کرده و آن‌را از طریق میکروفن دستگاه پخش می‌کند. برای اینکه برنامه به‌درستی کار کند، توسعه دهنده باید یک درخواست دسترسی به RECORD_AUDIOرا برای اجازه دسترسی به ضبط صدا، در فایل AndroidManifest.xml بفرستد. این کار به برنامه ما اجازه می‌دهد تا از مؤلفه‌های سیستم که ضبط صدا را کنترل می‌کنند، استفاده کنیم. یک سؤال اینجا مطرح می‌شود که «چه کسی تصمیم می‌گیرد که این دسترسی داده شود یا اینکه تکذیب شود؟»
فقط و فقط هسته‌ی اندروید به کاربر نهایی اجازه می‌دهد تا این فرآیند تایید نهایی را انجام دهد؛ نه هیچ سیستم دیگر یا کاربر دیگری! زمانیکه کاربر، برنامه ما را نصب می‌کند، این کار او را تشویق می‌کند تا این عملیات را انجام دهد. به این نکته دقت کنید که عجله ای برای این دسترسی وجود ندارد و هر زمانیکه برنامه نصب شد، اتفاق خواهد افتاد و این مجوز دسترسی تنها در زمان نصب به کاربر داده می‌شود نه قبل از آن. (به تصویر زیر دقت کنید)

 اگر ما درخواست خود را برای دسترسی به RECORD_AUDIOثبت نکنیم یا اگر صاحب دستگاه تائید نکند که پس از درخواست ما اجازه استفاده از آن را بدهد، آنگاه یک استثناء به حافظه‌ی مجازی (VM) داده خواهد شد و برنامه با شکست روبرو می‌شود. این مفهوم برای توسعه دهنده‌است که بداند در خواستی را صادر کرده است یا خیر؟ پس برای درخواست مجوز مورد نظر باید برچسب زیر، در فایل AndroidManifest.xml درج شود.
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Application Code Signing
هر برنامه‌ی کاربردی که بر روی سیستم‌عامل اندروید اجرا می‌شود، باید امضاء شود. اندروید از گواهی توسعه دهندگان فردی برای شناسایی آن‌ها و ایجاد روابط اعتمادسازی استفاده می‌کند که در میان کاربردهای مختلفی که در سیستم‌عامل اجرا می‌شوند پیاده می‌شود. 
سیستم‌عامل مجاز به اجرای برنامه‌ای بدون امضاء نخواهد بود. 
استفاده‌ی از یک مرجع، برای صدور امضای یک برنامه لازم نیست؛ چرا که سیستم امنیتی اندروید تنها از یک امضای تک گانه که توسعه دهنده در برنامه قرار داده‌است پیروی می‌کند! مجوزهای کنترلی برنامه مانند امضاء در طول نصب برنامه از طریق هسته‌ی اندروید بررسی شده و ضمانت و تایید خواهند شد. تمامی این فرآیندها به صورت خودکار و بلادرنگ در پشت پرده به سرعت اتفاق می‌افتند! در این صورت اگر گواهی اعتماد (certificate) توسط توسعه دهنده منقضی شود و یا به نحوی مورد اطمینان برای دستگاه نباشد، تمامی عملیات‌های ذکر شده بر روی برنامه به صورت عادی، ادامه خواهند یافت.

‫به روز رسانی تمام فیلدهای رشته‌ای تمام جداول بانک اطلاعاتی توسط Entity framework 6.x

$
0
0
یکی از مراحلی که پس از ارتقاء یک سایت به HTTPS باید صورت گیرد، به روز رسانی آدرس‌های قدیمی درج شده‌ی در صفحات مختلف، از HTTP به HTTPS است؛ وگرنه با خطای «قسمتی از صفحه امن نیست» توسط مرورگر مواجه خواهیم شد:


روش‌های زیادی برای مدیریت این مساله وجود دارند؛ مانند استفاده از ماژول‌های URL Rewrite برای بازنویسی آدرس‌های نهایی صفحه‌ی در حال رندر و یا ... به روز رسانی مستقیم بانک اطلاعاتی، یافتن تمام فیلدهای رشته‌ای ممکن در تمام جداول موجود و سپس اعمال تغییرات.


یافتن لیست تمام جداول قابل مدیریت توسط Entity framework

در ابتدا می‌خواهیم لیست پویای تمام جداول مدیریت شده‌ی توسط EF را پیدا کنیم. از این جهت که نمی‌خواهیم به ازای هر کدام یک کوئری جداگانه بنویسیم.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EFReplaceAll.Models;

namespace EFReplaceAll.Config
{
    public class DbSetInfo
    {
        public IQueryable<object> DbSet { set; get; }
        public Type DbSetType { set; get; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Product> Products { set; get; }
        public DbSet<Category> Categories { set; get; }
        public DbSet<User> Users { set; get; }

        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }

        public IList<DbSetInfo> GetAllDbSets()
        {
            return this.GetType()
                .GetProperties()
                .Where(p => p.PropertyType.IsGenericType &&
                            p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
                .Select(p => new DbSetInfo
                {
                    DbSet = (IQueryable<object>)p.GetValue(this, null),
                    DbSetType = p.PropertyType.GetGenericArguments().First()
                })
                .ToList();
        }
    }
}
در اینجا متد GetAllDbSets، به صورت پویا لیست DbSetها را به همراه نوع جنریک آن‌ها، بازگشت می‌دهد. با استفاده از این لیست می‌توان رکوردهای تمام جداول را واکشی و سپس تغییر داد.


یافتن فیلدهای رشته‌ای رکوردهای تمام جداول و سپس به روز رسانی آن‌ها

می‌خواهیم متدی را طراحی کنیم که در آن لیستی از یافتن‌ها و جایگزینی‌ها قابل تعیین باشد. به همین جهت مدل زیر را تعریف می‌کنیم:
using System;

namespace EFReplaceAll.Utils
{
    public class ReplaceOp
    {
        public string ToFind { set; get; }
        public string ToReplace { set; get; }
        public StringComparison Comparison { set; get; }
    }
}
در اینجا خاصیت Comparison امکان جستجو و جایگزینی غیرحساس به حروف کوچک و بزرگ را نیز میسر می‌کند.

سپس متدی که کار یافتن تمام فیلدهای رشته‌ای و سپس جایگزین کردن آن‌ها را انجام می‌دهد به صورت زیر خواهد بود:
using System.Collections.Generic;
using System.Linq;
using EFReplaceAll.Config;

namespace EFReplaceAll.Utils
{
    public static class UpdateDbContextContents
    {
        public static void ReplaceAllStringsAcrossTables(IList<ReplaceOp> replaceOps)
        {
            int dbSetsCount;
            using (var uow = new MyContext())
            {
                dbSetsCount = uow.GetAllDbSets().Count;
            }

            for (var i = 0; i < dbSetsCount; i++)
            {
                using (var uow = new MyContext()) // using a new context each time to free resources quickly.
                {
                    var dbSetResult = uow.GetAllDbSets()[i];
                    var stringProperties = dbSetResult.DbSetType.GetProperties()
                        .Where(p => p.PropertyType == typeof(string))
                        .ToList();
                    var dbSetEntities = dbSetResult.DbSet;
                    var haveChanges = false;
                    foreach (var entity in dbSetEntities)
                    {
                        foreach (var stringProperty in stringProperties)
                        {
                            var oldPropertyValue = stringProperty.GetValue(entity, null) as string;
                            if (string.IsNullOrWhiteSpace(oldPropertyValue))
                            {
                                continue;
                            }

                            var newPropertyValue = oldPropertyValue;
                            foreach (var replaceOp in replaceOps)
                            {
                                newPropertyValue = newPropertyValue.ReplaceString(replaceOp.ToFind, replaceOp.ToReplace, replaceOp.Comparison);
                            }
                            if (oldPropertyValue != newPropertyValue)
                            {
                                stringProperty.SetValue(entity, newPropertyValue, null);
                                haveChanges = true;
                            }
                        }
                    }

                    if (haveChanges)
                    {
                        uow.SaveChanges();
                    }
                }
            }
        }

    }
}
توضیحات:
- در اینجا using (var uow = new MyContext()) را زیاد مشاهده می‌کنید. علت اینجا است که اگر تنها با یک Context کار کنیم، EF تمام تغییرات و تمام رکوردهای وارد شده‌ی به آن‌را کش می‌کند و مصرف حافظه‌ی برنامه با توجه به خواندن تمام رکوردهای بانک اطلاعاتی توسط آن، ممکن است به چند گیگابایت برسد. به همین جهت از Contextهایی با طول عمر کوتاه استفاده شده‌است تا میزان مصرف RAM این متد سبب کرش برنامه نشود.
- در ابتدای کار توسط متد GetAllDbSets که به Context اضافه کردیم، تعداد DbSetهای موجود را پیدا می‌کنیم تا بتوان بر روی آن‌ها حلقه‌ای را تشکیل داد و به ازای هر کدام یک (()using (var uow = new MyContext را تشکیل داد.
- سپس با استفاده از نوع DbSet که توسط خاصیت dbSetResult.DbSetType در دسترس است، خواص رشته‌ای ممکن این DbSet یافت می‌شوند.
- در ادامه dbSetResult.DbSet یک Data Reader را به صورت پویا بر روی DbSet جاری باز کرده و تمام رکوردهای این DbSet را یک به یک بازگشت می‌دهد.
- در اینجا با استفاده از Reflection، از رکورد جاری، مقادیر خواص رشته‌ای آن دریافت شده و سپس کار جستجو و جایگزینی انجام می‌شود.
- در آخر هم فراخوانی uow.SaveChanges کار ثبت تغییرات صورت گرفته را انجام می‌دهد.


متدی برای جایگزینی غیرحساس به حروف بزرگ و کوچک

متد استاندارد Replace رشته‌ها، حساس به حروف بزرگ و کوچک است. یک نمونه‌ی عمومی‌تر را که در آن بتوان StringComparison.OrdinalIgnoreCase را تعیین کرد، در ذیل مشاهده می‌کنید که از آن در متد ReplaceAllStringsAcrossTables فوق استفاده شده‌است:
using System;
using System.Text;

namespace EFReplaceAll.Utils
{
    public static class StringExtensions
    {
        public static string ReplaceString(this string src, string oldValue, string newValue, StringComparison comparison)
        {
            if (string.IsNullOrWhiteSpace(src))
            {
                return src;
            }

            if (string.Compare(oldValue, newValue, comparison) == 0)
            {
                return src;
            }

            var sb = new StringBuilder();

            var previousIndex = 0;
            var index = src.IndexOf(oldValue, comparison);

            while (index != -1)
            {
                sb.Append(src.Substring(previousIndex, index - previousIndex));
                sb.Append(newValue);
                index += oldValue.Length;

                previousIndex = index;
                index = src.IndexOf(oldValue, index, comparison);
            }

            sb.Append(src.Substring(previousIndex));

            return sb.ToString();
        }
    }
}
و در آخر یک مثال از استفاده‌ی این متد تهیه شده، جهت به روز رسانی لینک‌های HTTP به HTTPS در تمام جداول برنامه به صورت زیر است:
            UpdateDbContextContents.ReplaceAllStringsAcrossTables(
                new[]
                {
                    new ReplaceOp
                    {
                        ToFind = "http://www.dotnettips.info",
                        ToReplace = "https://www.dotnettips.info",
                        Comparison = StringComparison.OrdinalIgnoreCase
                    },
                    new ReplaceOp
                    {
                        ToFind = "http://dotnettips.info",
                        ToReplace = "https://www.dotnettips.info",
                        Comparison = StringComparison.OrdinalIgnoreCase
                    }
                });

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: EFReplaceAll.zip

‫کامپوننت‌ها در Vue.js

$
0
0
پیش‌تر در سایت مطالبی در رابطه با فریم‌ورک Vue.js منتشر شده‌است. در این مطلب می‌خواهیم نگاهی بر مفهوم کامپوننت‌ها در Vue بیندازیم و نحوه‌ی استفاده از آنها را بررسی کنیم.

قبل از معرفی کامپوننت‌ها اجازه دهید سیستم template در ویو را بررسی کنیم. سیستم template ویو براساس سینتکس HTML است:
new Vue({
  el: '#app',
  template: '<div>Hello DNT</div>'
});
البته استفاده از template کاملاً اختیاری است. بجای آن می‌توانیم از تابع رندر (همانند React) نیز استفاده کنیم:
new Vue({
    el: '#app',
    data() {
        return {
            blogTitle: 'DNT'
        }
    },
    render: function (createElement) {
        return createElement('h1', this.blogTitle)
    }
});

ایجاد یک کامپوننت ساده:
Vue.component('child', {
    template: '<div>Hello DNT users</div>'
});
در اینجا برای ایجاد یک کامپوننت، از تابع component استفاده کرده‌ایم؛ پارامتر اول این تابع، نام کامپوننت است و پارامتر دوم نیز یک شیء است. درون این شیء می‌توانیم قالب کامپوننت را تعیین کنیم. برای کامپوننت نیز می‌توانیم یک پارامتر ورودی را تعیین کنیم. اینکار را توسط مفهومی به نام Props می‌توانیم انجام دهیم:
Vue.component('child', {
    props: ['text'],
    template: `<div> {{ text }} </div>`
});

new Vue({
    el: '#app',
    data() {
        return {
            message: 'Hello DNT!'
        }
    }
});
اکنون می‌توانیم پارامتر موردنظر را به text، به عنوان ورودی کامپوننت ارسال کنیم (در واقع دیتای موجود در parent را به کامپوننت child ارسال کرده‌ایم):
<child :text="message"></child>

اعتبارسنجی پراپرتی‌ها
برای props می‌توانیم اعتبارسنجی را نیز انجام دهیم:
Vue.component('blogPost', {
    props: {
        post: {
            type: Object,
            required: true
        }
    },
    template: `<div><h1>{{ post.title }}</h1><p>{{ post.body }}</p></div>`
});
در اینجا نوع خاصیت post باید شیء باشد و همچنین آن را به صورت required تعریف کرده‌ایم. در این حالت اگر مقداری به غیر از شیء را به آن ارسال کنیم، خطای زیر را در کنسول دریافت خواهیم کرد:
[Vue warn]: Invalid prop: type check failed for prop "post". Expected Object, got String.

found in

---> <BlogPost><Root>

همچنین می‌توانیم نوع اعتبارسنجی را به صورت سفارشی نیز تعیین کنیم:
Vue.component('blogPost', {
    props: {
        post: {
            type: Object,
            required: true,
            validator: obj => {
                const titleIsValid = typeof obj.title === 'string';
                const bodyIsValid = typeof obj.body === 'string';
                const isValid = titleIsValid && bodyIsValid;
                if (!isValid) {
                    console.warn("prop is not valid");
                    return false;
                }
                return true;
            }
        }
    },
    template: `<div><h1>{{ post.title }}</h1><p>{{ post.body }}</p></div>`
});

تعیین مقدار پیش‌فرض برای پراپرتی
برای یک prop می‌توانیم مقدار پیش‌فرضی را نیز تعیین کنیم. یعنی در صورت عدم ارسال شیء می‌توانیم تعیین کنیم که چه شیء‌ایی در حالت پیش‌فرض نمایش داده شود:
Vue.component('blogPost', {
    props: {
        post: {
            type: Object,
            validator: obj => {
                const titleIsValid = typeof obj.title === 'string';
                const bodyIsValid = typeof obj.body === 'string';
                const isValid = titleIsValid && bodyIsValid;
                if (!isValid) {
                    console.warn("prop is not valid");
                    return false;
                }
                return true;
            },
            default: function() {
                return {
                    title: 'Vue is fun!',
                    body: 'Vue is fun..................'
                }
            }
        }
    },
    template: `<div><h1>{{ post.title }}</h1><p>{{ post.body }}</p></div>`
});

استفاده از دیتا درون کامپوننت
درون یک کامپوننت نیز می‌توانیم یکسری دیتا را تعریف کنیم. اما باید در نظر داشته باشید که هر وهله از کامپوننت، scope مجزای خودش را دارد. در نتیجه پیشنهاد میشود دیتا حتماً به صورت یک تابع تعریف شود و همچنین داده‌های درون آن نیز در scope کامپوننت تعریف شوند:
data: function () {
    return {
        stars: 5,
        hover: 5
    }
},
زیرا در صورت تعریف داده‌ها در خارج از scope کامپوننت، محتویات دیتا برای دیگر وهله‌های کامپوننت‌ها نیز به اشتراک گذاشته می‌شود. به عنوان مثال فرض کنید درون کامپوننت بلاگ‌پست، یک سیستم امتیاز دهی قرار داده‌ایم:
var dt = {
    stars: 5,
    hover: 5
};
Vue.component('blogPost', {
    data: function() {
        return dt;
    },
    props:  // as before...,

    template: `<div class="blog-post"><h1>{{ post.title }}</h1><p>{{ post.body }}</p><div class="star-wrap"><span v-for="n in 5"
                            class="star"
                            :class="{ full: hover >= n+1 }"
                            @click="stars = n+1"
                            @mouseover="hover = n+1"
                            @mouseout="hover = stars"></span></div></div>`
});
در این حالت خروجی زیر را خواهیم داشت:



برای رفع این مشکل کافی است به اینصورت دیتا را تعریف کنیم:

Vue.component('blogPost', {
    data: function() {
        return {
             stars: 5,
             hover: 5
        }
    },
    props:  // as before...,
    template: // as before
});


تغییر دیتا درون کامپوننت‌ها

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

Vue.component('child', {
    props: ['message'],
    methods: {
        changeName() {
            this.message = "New Name!..."
        }
    },
    template: '#child-template'
});

new Vue({
    el: '#app',
    data() {
        return {
            name: 'DNT!'
        }
    }
});

تمپلیت کامپوننت فوق نیز به صورت x-template درون DOM تعریف شده است:

<script type="text/x-template" id="child-template"><div><p>{{ message }}</p><button @click="changeName">Change Name</button></div></script>


فراخوانی کامپوننت نیز به اینصورت می‌باشد:

<div id="app"><child :message="name"></child></div>

همانطور که مشاهده می‌کنید، دیتای name را از طریق ویژگی message توانسته‌ایم به کامپوننت child ارسال کنیم. درون تمپلیت آن نیز یک دکمه را برای تغییر مقدار این ویژگی تعریف کرده‌ایم. تغییر این ویژگی نیز یک assignment ساده است. اما اگر بر روی دکمه‌ی Change Name کلیک کنید، هشدار زیر را درون کنسول مشاهده خواهید کرد:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message"

found in

---> <Child><Root>

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

changeName() {
    this.message = "New Name!...",
    this.$emit("change-name", this.message);
}

برای تگ child نیز این ایونت را اضافه خواهیم کرد:

<child :message="name" @change-name="name = $event"></child>

در اینحالت با تغییر ویژگی message، مقدار دیتای name نیز بلافاصله تغییر پیدا خواهد کرد.


Slots

Slot یک روش عالی برای جایگزینی محتوای درون یک کامپوننت است. فرض کنید می‌خواهیم کامپوننت‌مان به صورت زیر باشد:

<modal>
    Hello</modal>

در اینحالت باید درون تمپلیت مکان قرارگیری Hello را تعیین کنیم. اینکار را می‌توانیم با قرار دادن تگ slot انجام دهیم:

Vue.component('modal', {
    template: `
            ...<div class="modal-body"><slot></slot></div>
            ...
    `
});

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

Vue.component('modal', {
    template: `<div class="modal fade" id="detailsModal" tabindex="-1" role="dialog" aria-labelledby="detailsModalLabel" aria-hidden="false"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="detailsModalLabel"><slot name="title"></slot></h5><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"><slot name="body"></slot></div><div class="modal-footer"><slot name="footer"></slot></div></div></div></div>
    `
});

اکنون می‌توانیم محتوای مورد نظر را برای قرارگیری درون slotها تعیین کنیم:

<modal><template slot="title">Title</template><template slot="body">Lorem ipsum dolor sit amet.</template><template slot="footer"><button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button><button type="button" class="btn btn-primary">Save changes</button></template></modal>


‫چک لیست ارتقاء به HTTPS مخصوص یک برنامه‌ی ASP.NET MVC 5x

$
0
0
پس از فعالسازی HTTPS بر روی سایت خود، در جهت بهبود امنیت برنامه‌های ASP.NET MVC 5.x، می‌توان درخواست کوکی‌های صرفا ارسال شده‌ی از طریق اتصال‌های HTTPS، اجبار به استفاده‌ی از آدرس‌های HTTPS و هدایت خودکار به آدرس‌های HTTPS را نیز فعالسازی کرد.


کوکی‌هایی که باید HTTPS only شوند

کوکی‌های پیش‌فرض برنامه‌های ASP.NET به صورت HTTP Only به سمت کلاینت ارسال می‌شوند. این کوکی‌ها توسط اسکریپت‌ها قابل خوانده شدن نیستند و به همین جهت یکی از راه‌های مقاومت بیشتر در برابر حملات XSS به شمار می‌روند. پس از ارتقاء به HTTPS، این کوکی‌ها را هم می‌توان HTTPs Only کرد تا فقط به کلاینت‌هایی که از طریق آدرس HTTPS سایت به آن وارد شده‌اند، ارائه شود:
1) کوکی آنتی‌فورجری توکن
 AntiForgeryConfig.RequireSsl = true;
محل تنظیم در متد Application_Start
2) کوکی‌های حالت Forms Authentication
<configuration><system.web><authentication mode="Forms"><forms requireSSL="true" cookieless="UseCookies"/></authentication></system.web></configuration>

3) تمام httpCookies
<configuration><system.web><httpCookies httpOnlyCookies="true" requireSSL="true" /></system.web></configuration>

4) کوکی‌های Role manager
<configuration><system.web><roleManager cookieRequireSSL="true" /></system.web></configuration>
محل تنظیم این سه مورد در فایل web.config است.

5) کوکی‌های OWIN Authentication و ASP.NET Identity 2.x
var options = new CookieAuthenticationOptions()
{
    CookieHttpOnly = true,
    CookieSecure = CookieSecureOption.Always,
    ExpireTimeSpan = TimeSpan.FromMinutes(10)
};


فعالسازی اجبار به استفاده‌ی از HTTPS

با استفاده از فیلتر RequireHttps، دسترسی به تمام اکشن متدهای برنامه تنها به صورت HTTPS میسر خواهد شد:
 filters.Add(new RequireHttpsAttribute(permanent: true));
محل تنظیم در متد RegisterGlobalFilters فایل Global.asax.cs، و یا در کلاس FilterConfig به صورت زیر:
using System.Web.Mvc;
namespace MyWebsite
{
    internal static class FilterConfig
    {
        internal static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new RequireHttpsAttribute(permanent: true));
        }
    }
}
پارامتر permanent آن در چند به روز رسانی آخر MVC 5 به آن اضافه شده‌است (از نگارش 5.2.4 به بعد) و مخصوص موتورهای جستجو است؛ در جهت تصحیح خودکار آدرس‌های قدیمی سایت به آدرس‌های جدید.

همچنین در متد Application_BeginRequest نیز می‌توان بررسی کرد که آیا درخواست ارسالی یک درخواست HTTPS است یا خیر؟ اگر خیر، می‌توان کاربر را به صورت خودکار به نگارش HTTPS آن هدایت دائم کرد:
        protected void Application_BeginRequest(Object sender, EventArgs e)
        {
            if (!HttpContext.Current.Request.IsSecureConnection)
            {
                var builder = new UriBuilder
                {
                    Scheme = "https",
                    Host = Request.Url.Host,
                    // use the RawUrl since it works with URL Rewriting
                    Path = Request.RawUrl
                };
                Response.Status = "301 Moved Permanently";
                Response.AddHeader("Location", builder.ToString());
            }
        }
جهت محکم کاری این قسمت می‌توان دو تنظیم تکمیلی ذیل را نیز به فایل web.config اضافه کرد:
فعالسازی HSTS جهت اطلاع به مرورگر که این سایت تنها از طریق HTTPS قابل دسترسی است و تمام درخواست‌های HTTP را به صورت خودکار از طریق HTTPS انجام بده:
<httpProtocol><customHeaders><add name="Strict-Transport-Security" value="max-age=16070400; includeSubDomains" />
و همچنین اگر ماژول URL Rewite بر روی سرور نصب است، کار هدایت خودکار به آدرس‌های HTTPS را نیز می‌توان توسط آن مدیریت کرد:
<rewrite><rules>        <rule name="Redirect to HTTPS" stopProcessing="true"><match url="(.*)" /><conditions><add input="{HTTPS}" pattern="off" ignoreCase="true" /><add input="{HTTP_HOST}" negate="true" pattern="localhost" /></conditions><action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" /></rule>


اصلاح تمام آدرس‌های مطلقی که توسط برنامه تولید می‌شوند

اگر در برنامه‌ی خود از Url.Action برای تولید آدرس‌ها استفاده می‌کنید، با ذکر پارامتر protocol آن، آدرس تولیدی آن بجای یک مسیر نسبی، یک مسیر مطلق خواهد بود. اگر پیشتر این پروتکل را به صورت دستی به http تنظیم کرده‌اید، روش صحیح آن به صورت زیر است که با آدرس جدید HTTPS سایت هم سازگار است:
 var fullBaseUrl = Url.Action(result: MVC.Home.Index(), protocol: this.Request.Url.Scheme);


اصلاح تمام آدرس‌هایی که پیشتر توسط برنامه تولید شده‌اند

یک نمونه آن در مطلب «به روز رسانی تمام فیلدهای رشته‌ای تمام جداول بانک اطلاعاتی توسط Entity framework 6.x» بحث شده‌است.


اصلاح فایل robots.txt و درج آدرس HTTPS جدید

اگر در فایل robots.txt سایت، آدرس مطلق Sitemap را به صورت HTTP درج کرده بودید، آن‌را به HTTPS تغییر دهید:
User-agent: *
Sitemap: https://www.dotnettips.info/Sitemap

‫MongoDb در سی شارپ (بخش نهم)

$
0
0
سال‌های مدیدی است که به طراحی پایگاه‌های sql پرداخته و تجاربی آموخته‌ایم. کتاب‌ها و مقالات زیادی در اینباره منتشر شده‌اند. از این‌رو در نحوه طراحی دیتابیس‌های رابطه‌ای اطلاعات زیادی کسب و مسائل زیادی را از این راه حل نموده‌ایم؛ ولی با ورود دیتابیس‌های NoSql و تنوع زیاد آن‌ها و روش‌های متنوعی که هر کدام از آن‌ها به طور جداگانه دارند باعث شد تجربه سال‌ها فعالیت و مدل ذهنی که داشتیم به یکباره تغییر کند و گاها بیشتر باعث گیج شدن می‌گردد. از این‌رو در این مقاله سعی داریم تکنیک‌ها مدل سازی اسناد را در دیتابیس مونگو، بررسی کنیم و مزایا و معایب هر یک را برشماریم.
در دیتابیس‌های قدیم، تمرکز بر روی نوشتن بود تا با کمترین افزونگی و تکرار و رعایت اصول ACID، اطلاعات را ذخیره نماییم. ولی در حال حاضر به دلیل دسترسی به فضاهای ذخیره سازی بزرگتر و همچنین افزایش ترافیک شبکه در واکشی دیتاها، قضیه عکس شده و تمرکز دیتابیس‌های NoSql بر روی خواندن میباشد. پس باید فاکتورهای مدل سازی طوری باشد تا خواندن در سریعترین حد امکان قرار بگیرد. البته مواردی چون حذف و به روزرسانی هم باید در این مورد بررسی شوند.
ارتباط اسناد با یکدیگر:
ارتباط اسناد از دو طریق امکان پذیر است:
  • حالت ارجاع : شماره سند یا Object Id را شامل شده و در صورتیکه به اطلاعاتی نیاز داشتید، باید اطلاعات آن را در یک درخواست جداگانه واکشی نمایید. چون مونگو شامل جوین نبوده و جوین‌ها باید در سطح اپلیکیشن مدیریت شوند.
{
fname:'ali',
lname:'yeganeh',
accounts:[454354353,3455435]
}

  • حالت جاسازی سند (یا اسناد تو در تو) Embed : در این حالت سند مورد نظر اطلاعات سند دیگری را در درون خود نگه میدارد. در این حالت به هیچ جوینی نیازی نیست و اطلاعات وابسته، به همراه خود سند اصلی واکشی می‌شوند. این نکته باید مورد توجه قرار بگیرد که مونگو یک دیتابیس غیر اتمیک هست و در صورتیکه اصل دیتا تغییر کند، تغییر یا به روزرسانی در سندهای Embed انجام نخواهد شد و در صورت نیاز باید خودتان به طور دستی آن را کنترل نمایید.
{
fname:'ali',
lname:'yeganeh',
accounts:[
{
  username:"ali",
  password:"123"
},
{
  username:"reza",
  password:"456"
}
]
}

مدل هایی با ارتباط یک به یک : 
در این نوع مدل سازی، دو سند داریم که یکی از آن‌ها Principle و دیگری Dependent محسوب می‌شود. برای ذخیره سازی آن‌ها عموما از حالت Embed استفاده میشود. در این حالت چون ارتباط بین دو سند به صورت یک به یک میباشد، در واقع این امکان وجود دارد تا سند مادری به طور جداگانه وجود نداشته باشد و همان سند به صورت Embed ذخیره میشود. در این حالت مشکلی از لحاظ اتمیک نبودن مونگو پیش نمیاید و  ویرایش راحت‌تری خواهد داشت.
مدل‌هایی با ارتباط یک به چند:
این اسناد را می‌توان به دو حالت بالا بر حسب نیازمندی سیستم ذخیره کرد. فرض کنید مثال زیر را که در سایت مونگو هم عنوان شده‌‌است، داریم:
book
{
     name:'Scarlet Letter",
     Language:"English",
     Pages:124,
...
}

publisher
{
   name : "Orielly",
   ...

}
در این حالت هر کتاب باید ارتباطی با ناشر خود داشته باشد. در صورتیکه به صورت Embed داخل سند قرار بگیرد و هر کتابی شامل اطلاعات ناشر خود باشد، نکات زیر مورد بررسی قرار میگیرند:
book
{
     name:'Scarlet Letter",
     Language:"English",
     Pages:124,
...,
publisher:
{
   name : "Orielly",
   ...

}
}

نکات مثبت:
  1. در این حالت در صورتیکه واکشی هر کتاب به همراه اطلاعات ناشر را نیاز داشته باشیم و یا پرس وجوهای ترکیبی نیاز باشد، در سریعترین زمان ممکن واکشی انجام خواهد شد.
  2. درج و مدیریت آن راحت‌تر خواهد بود.
نکات منفی:
  1. در صورتیکه اطلاعات ناشر نیاز به تغییرات اساسی داشته باشد و باید در تمامی سندها اصلاح گردد، باید تمامی اسناد مربوط به اطلاعات کتاب به روزرسانی شوند که هزینه سنگین‌تری را خواهد داشت.
  2. دیتای تکراری زیادی ذخیره خواهد شد و در نتیجه حافظه بیشتری را میطلبد.
  3. در صورتیکه تنها به اطلاعات ناشر نیاز باشد و اطلاعات ناشر در سند دیگری وجود نداشته باشد و فقط در سند کتاب وجود داشته باشد، واکشی آن هزینه سنگین‌تری را خواهد طلبید. به همین جهت توصیه میشود در صورتیکه دیتای شما می‌تواند به صورت یک موجودیت مستقل هم عمل کند، اطلاعات آن در سند دیگری که من به آن سند اصلی میگویم ذخیره شوند تا نمونه‌ها از روی آخرین ویرایش آن ساخته شوند و موقعی‌که تنها به واکشی آن اطلاعات نیاز است، همان‌ها بیرون کشیده شوند.
در روشی دیگری میتوان ارجاعی از ناشر را به شکل زیر در کتاب نگهداری کرد:
book
{
     name:'Scarlet Letter",
     Language:"English",
     Pages:124,
...,
publisher:1212121
}
نکات مثبت:
  1. عدم وجود تکرار اطلاعات
  2. چون تنها یک سند برای ویرایش وجود دارد، نیازی به اصلاح اسناد توکار نیست و ویرایش، هزینه کمتری خواهد داشت.
نکات منفی:
  1. عدم وجود جوین: در صورتیکه نیاز به جوین بزرگی باشد، این نوع جوین باید در سطح برنامه شما انجام شود و هزینه بر خواهد بود.

نگهداری نام کتاب‌ها در ناشر
انعطاف مونگو برای ایجاد مدل، گزینه‌های زیادی را پیش رو میگذارد و واقعا مدلسازی را بیشتر از قبل، چالش برانگیز میکند. در حالت دیگر میتوان اطلاعات کتاب را به صورت ارجاع، در سند ناشر نگهداری کرد. به عنوان مثال زمانیکه نیاز داریم کتب منتشرشده یک ناشر را ببینیم، شاید این گزینه بهتر باشد. البته در این حالت باید بتوان ارجاعات به کتاب را در تعداد محدودی نگهداری کرد؛ در غیر این صورت با تعداد زیادی ارجاع که شاید هیچگاه نیازی هم به آن‌ها نیست، خواهیم رسید و در این حالت شاید ارجاع به ناشر در سند کتاب بسیار بهتر به نظر برسد. البته میتوان در این حالت ناشر تنها به تعداد معدودی از آخرین کتابهایش دسترسی داشته باشد تا کاربر بتواند آخرین کتاب‌های منتشر شده‌ی ناشر را ببیند. 
حال با اطلاعات بالا چگونه مدلسازی کنیم؟
همانطور که گفتیم ابتدا تمرکز شما باید برای خواندن اطلاعات باشد و سپس معیارهایی چون به روزرسانی نیز بررسی گردند. به عنوان نمونه اطلاعات یک پست در وبلاگ را در نظر بگیرید. این سند شامل سندهای توکاری چون دسته بندی، اطلاعات نویسنده، معیارهایی چون امتیازدهی و بخش نظرات میباشد. در این حالت چون همه عناصر قرار است با یکدیگر بیرون کشیده شوند و در واقع تنها با یک سند سروکار داریم، کار بسیار سریعتر و راحت‌تر است. پس این ساختار گزینه مناسبی برای نمایش است:
Post
{
title:"C#",
body:"About C#",
tags:['C#','.Net','microsoft'],
Categories:[{name:'Programming'}],
votes:[{rate:3,user:42342},{rate:5,user:423445},...],
comments:[
{
text:"my comment1",
time:"10/2/1396",...},
...

]
}

حال این تصور را داشته باشید که ما تنها یک پست را نشان نمیدهیم و بلکه پست‌ها به صورت یک لیست قرار است نمایش داده شوند و با گزینه‌ی مشاهده‌ی مطلب می‌توانیم یک پست را به صورت کامل ببینیم. در این صورت همه اطلاعات همانند قبل هستند، بجز بخش نظرات که دیگر در این حالت کاربردی ندارد و دیتای اضافی است که به ناچار باید خوانده شود. پس در این حالت میگوییم این مدل برای خواندن مناسب نیست، چون باید تمام نظرات اسنادی که در لیست قرار دارند هم خوانده شوند. پس باید بخش نظرات را از سند پست وبلاگ جدا کنیم.
{
POST:45453,
count:35,
comments:[...]
}
سپس میگوییم هر سند نهایتا 16 مگابایت اطلاعات را نگهداری میکند و هم اینکه تعداد نظرات ممکن است بسیار زیاد باشند. پس هر سند را به تعدادی نظر محدود میکنیم به این حالت میگویند داریم یک Bucket میسازیم و مثلا هر باکت را به 100 کامنت محدود میکنیم. تا به الان وضعیت طراحی بهتری نسبت به قبل پیدا کردیم:
{
post:345345,
capacity:100,
count:35,
bucket:2,
comments:[...]
}
در این حالت حتی میتوانیم کامنتها را صفحه بندی کرده و در هر صفحه یک باکت را بخوانیم. برای نمایش این دو مورد آخری برای جداسازی دیتا بسیار خوب است. حتی میتوان یک کامنت را به همراه پاسخ‌های آن که به صورت درخت واره قرار گرفته اند نیز در یک سند جداگانه ذخیره کرد.
نکاتی که باید در حین طراحی در نظر بگیرید:
  1. همیشه به این نکته توجه داشته باشید که نباید بگذارید تعداد آرایه‌های یک سند خیلی بزرگ شوند. در غیر اینصورت کارآیی مونگو به خصوص در حین ویرایش سند پایین خواهد آمد. در حین ویرایش، اگر سندی از اندازه‌ی خود بزرگتر نشود، مشکلی پیش نمیاید ولی اگر فضایی بیش از آنچه که  قبلا داشته به آن اضافه شود، سند نیاز به جابجایی و گسترش فضا خواهد داشت. در این حالت باید مونگو سند را به جای دیگری که فضای کافی برای آن وجود دارد، انتقال بدهد و میزان Disk Fragment به طبع بالا خواهد رفت. همچنین اندیس‌های آرایه‌ای هم با جابجا شدن دیتا نیاز به، به روزرسانی خواهند داشت و زمانی هم صرف به روزرسانی اندیس‌ها خواهد شد.
  2. مدیر محصول مونگو اظهار نظر صریحی در این مورد نکرد‌ه‌است، ولی به نظر می‌رسد نوع فرمت BSON از یک اسکن خطی در حافظه استفاده میکند و زمان بیشتری صرف پیدا کردن المان‌های انتهایی در آرایه خواهد شد؛ پس بیشتر عملیات در این نوع سند، با کندی مواجه خواهند شد. با توجه به کامنت‌هایی که در سایت‌ها و شبکه‌های اجتماعی یافت شده‌است، آرایه ای با بیش از صدهزار آیتم ساده میتواند آسیب زا باشد؛ به همین دلیل توصیه میشود که اگر بیش از صدهزار آیتم نیاز است، از همان حالت Bucket استفاده شود.
  3. استفاده از اندیس‌هاهم سابقه‌ی دیرینه‌ای داشته و سعی کنید کوئری هایی بزنید که بر اساس اندیس‌های تعریف شده باشند تا واکشی دیتا سریعتر شود. پس نحوه کوئری نویسی و انتخاب فیلدی که اندیس میشود بسیار مهم است.
  4. استفاده از Projection تاثیری بر خواندن اسناد ندارد و هر سند به طور کامل واکشی می‌شود. projection تنها در بار‌ه‌ی ترافیک یا انتقال حجم کمتری از اطلاعات به سمت کلاینت تاثیرگذار میباشد. پس استفاده از projection بجای جدا سازی اسناد را دنبال نکنید.

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

$
0
0
ایمن کردن برنامه تولید شده در برابر حملات:

هنگام دریافت اطلاعات از کاربر، باید داده‌ها در جایی ذخیره شوند. اینکه داده‌ها در کجا ذخیره و نگه داری شوند و از نفوذ به آنها جلوگیری شود، نهایت امن بودن برنامه شما را نشان میدهد. باید فرض کنید که برنامه شما به طور مستقیم یا غیرمستقیم در برخی موارد مورد حمله قرار میگیرد و تنها چیزی که بین حفاظت از اطلاعات کاربر نهایی، شما و حفاظت از داده‌ها مطرح می‌شود برعهده شما خواهد بود. چند نمونه از حملات را توضیح خواهم داد:
حملات غیر مستقیم (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

$
0
0
در دنیای واقعی، تمام درخواست‌های HTTP ارسالی به سمت سرور، با موفقیت به آن نمی‌رسند. ممکن است در یک لحظه سرور در دسترس نباشد. در لحظه‌ای دیگر آنقدر بار آن بالا باشد که نتواند درخواست شما را پردازش کند و یا ممکن است درست در لحظه‌ای که توکن دسترسی به برنامه در حال به روز رسانی است، درخواست دیگری به سمت سرور ارسال شده باشد که حتما برگشت خواهد خورد؛ چون حاوی توکن جدید صادر شده نیست. در تمام این موارد ضرورت تکرار و سعی مجدد درخواست‌های شکست خورده وجود دارد. برای مدیریت این مساله در برنامه‌های Angular می‌توان از امکانات توکار کتابخانه‌ی RxJS به همراه آن کمک گرفت.


سعی مجدد خودکار درخواست‌ها توسط کتابخانه‌ی RxJS

با استفاده از عملگر retry می‌توان به صورت خودکار، درخواست‌های شکست خورده را به تعداد باری که مشخص می‌شود، تکرار کرد:
import { Observable } from "rxjs";
import { catchError, map, retry } from "rxjs/operators";

postEmployeeForm(employee: Employee): Observable<Employee> {
    const headers = new HttpHeaders({ "Content-Type": "application/json" });
    return this.http
      .post(this.baseUrl, employee, { headers: headers })
      .pipe(
        map((response: any) => response["fields"] || {}),
        catchError(this.handleError),
        retry(3)
      );
  }
در اینجا اگر درخواست اول با شکست مواجه شود، اصل آن‌را سه بار دیگر به سمت سرور ارسال می‌کند (البته در صورت بروز و دریافت خطای مجدد).
مشکل این روش در عدم وجود مکثی بین درخواست‌ها است. در اینجا تمام درخواست‌های سعی مجدد، بلافاصله به سمت سرور ارسال می‌شوند. همچنین نمی‌توان مشخص کرد که اگر مثلا خطای timeout وجود داشت، اینکار را تکرار کن و نه برای سایر حالات.


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

برای اینکه بتوان کنترل بیشتری را بر روی سعی‌های مجدد انجام شده داشت، می‌توان از عملگر retryWhen بجای retry استفاده کرد:
import { Observable, of, throwError as observableThrowError, throwError } from "rxjs";
import { catchError, delay, map, retryWhen, take } from "rxjs/operators";

postEmployeeForm(employee: Employee): Observable<Employee> {
    const headers = new HttpHeaders({ "Content-Type": "application/json" });
    return this.http
      .post(this.baseUrl, employee, { headers: headers })
      .pipe(
        map((response: any) => response["fields"] || {}),
        retryWhen(errors => errors.pipe(
          delay(1000),
          take(3)
        )),
        catchError(this.handleError)
      );
  }
در اینجا توسط عملگر retryWhen، کار سفارشی سازی سعی‌های مجدد درخواست‌های شکست خورده انجام شده‌است. در این مثال پس از هر درخواست مجدد، 1000ms صبر شده و سپس درخواست دیگری درصورت وجود خطا، به سمت سرور تا حداکثر 3 بار، ارسال می‌شود.

در ادامه اگر بخواهیم صرفا به خطاهای خاصی واکنش نشان دهیم می‌توان به صورت زیر عمل کرد:
import { Observable, of, throwError as observableThrowError, throwError } from "rxjs";
import { catchError, delay, map, mergeMap, retryWhen, take } from "rxjs/operators";

postEmployeeForm(employee: Employee): Observable<Employee> {
    const headers = new HttpHeaders({ "Content-Type": "application/json" });
    return this.http
      .post(this.baseUrl, employee, { headers: headers })
      .pipe(
        map((response: any) => response["fields"] || {}),
        retryWhen(errors => errors.pipe(
          mergeMap((error: HttpErrorResponse, retryAttempt: number) => {
            if (retryAttempt === 3 - 1) {
              console.log(`HTTP call failed after 3 retries.`);
              return throwError(error); // no retry
            }
            switch (error.status) {
              case 400:
              case 404:
                return throwError(error); // no retry
            }
            return of(error); // retry
          }),
          delay(1000),
          take(3)
        )),
        catchError(this.handleError)
      );
  }
در اینجا با اضافه شدن یک mergeMap، پیش از ارسال درخواست مجدد، به اطلاعات خطای رسیده‌ی از سمت سرور دسترسی خواهیم داشت. همچنین پارامتر دوم mergeMap، شماره سعی جاری را نیز بر می‌گرداند.
در داخل mergeMap اگر یک Observable معمولی بازگشت داده شود، به معنای صدور مجوز سعی مجدد است؛ اما اگر throwError بازگشت داده شود، دقیقا در همان لحظه کار retryWhen و سعی‌های مجدد خاتمه خواهد یافت. برای مثال در اینجا پس از 2 بار سعی مجدد، اصل خطا صادر می‌شود که سبب خواهد شد قسمت catchError اجرا شود و یا روش صرفنظر کردن از خطاهای با شماره‌های 400 یا 404 را نیز مشاهده می‌کنید. برای مثال اگر از سمت سرور خطای 404 و یا «یافت نشد» صادر شد، return throwError سبب خاتمه‌ی سعی‌های مجدد و خاتمه‌ی عملیات retryWhen می‌شود.


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

روش فوق را باید به ازای تک تک درخواست‌های HTTP برنامه تکرار کنیم. برای مدیریت یک چنین اعمال تکراری در برنامه‌های Angular می‌توان یک HttpInterceptor سفارشی را تدارک دید و توسط آن تمام درخواست‌های HTTP سراسر برنامه را به صورت متمرکز تحت نظر قرار داد:
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, of, throwError } from "rxjs";
import { catchError, delay, mergeMap, retryWhen, take } from "rxjs/operators";

@Injectable()
export class RetryInterceptor implements HttpInterceptor {

  private delayBetweenRetriesMs = 1000;
  private numberOfRetries = 3;

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      retryWhen(errors => errors.pipe(
        mergeMap((error: HttpErrorResponse, retryAttempt: number) => {
          if (retryAttempt === this.numberOfRetries - 1) {
            console.log(`HTTP call '${request.method} ${request.url}' failed after ${this.numberOfRetries} retries.`);
            return throwError(error); // no retry
          }

          switch (error.status) {
            case 400:
            case 404:
              return throwError(error); // no retry
          }

          return of(error); // retry
        }),
        delay(this.delayBetweenRetriesMs),
        take(this.numberOfRetries)
      )),
      catchError((error: any, caught: Observable<HttpEvent<any>>) => {
        console.error({ error, caught });
        if (error.status === 401 || error.status === 403) {
          // this.router.navigate(["/accessDenied"]);
        }
        return throwError(error);
      })
    );
  }
}
RetryInterceptor فوق، تمام درخواست‌های با شکست مواجه شده را دو بار با فاصله زمانی یک ثانیه تکرار می‌کند. البته در این بین همانطور که توضیح داده شد، از خطاهای 400 و 404 صرفنظر خواهد شد. همچنین در پایان کار اگر سعی‌های مجدد با موفقیت به پایان نرسند، قسمت catchError، اصل خطای رخ داده را دریافت می‌کند که در اینجا نیز می‌توان به این خطا عکس العمل نشان داد.
روش ثبت آن در قسمت providers مربوط به core.module.ts به صورت زیر است:
providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: RetryInterceptor,
      multi: true
    },

Vue CLI

$
0
0
تیم Vue یک ابزار را جهت scaffold سریع یک پروژه Vue، به صورت رسمی ارائه کرده‌است. توسط این ابزار به صورت سریع می‌توانیم ساختار یک پروژه استاندارد Vue را ایجاد کنیم.

چرا نیاز به Vue CLI داریم؟
  • زیرا نیاز به build processهایی داریم که به ما امکان استفاده از ES6, SCSS و دیگر ویژگیهای عالی را خواهند داد.
  • جهت ساخت و یکی‌سازی فایل‌های تمپلیت
  • بارگذاری نکردن تمامی فایل‌ها به صورت یکجا در زمان Startup 
  • می‌توانیم تسک‌هایی از قبیل Server-side rendering, code-splitting را انجام دهیم.
 
نصب Vue CLI 
ابتدا مطمئن شوید که آخرین نگارش Node.js را نصب کرده‌اید. سپس جهت نصب Vue CLI، خط فرمان را گشوده و دستور زیر ذیل را صادر کنید: 
npm install -g vue-cli

با اجرای فرمان فوق، ابزار CLI به صورت global و عمومی نصب خواهد شد. در ادامه می‌توانیم با دستور vue list، لیستی از قالب‌های رسمی را که توسط CLI قابل ایجاد هستند، مشاهده نمائید: 

در اینجا ما از قالب webpack-simple استفاده خواهیم کرد. برای اینکار دستور زیر را جهت ایجاد یک پروژه بر اساس این قالب صادر کنید: 
vue init webpack-simple dntVue

به این ترتیب در سریعترین زمان ممکن توانستیم یک برنامه‌ی Vue را ایجاد کنیم: 

در اینجا ساختار یک پروژه جدید Vue را مشاهده می‌کنید:

index.html: کار شروع و ارائه برنامه را انجام می‌دهد.
package.json: وابستگی‌های npm برنامه را به همراه دارد.
src/App.vue: کامپوننت اصلی برنامه است.
پوشه src/assets: حاوی فایل‌های استاتیک پروژه است.
src/main.js: نقطه‌ی آغاز برنامه است.
webpack.config.json: تنظیمات وب‌پک جهت اجرای پروژه و بارگذاری ماژول‌های موردنیاز.


اجرای برنامه
ابتدا نیاز است وابستگی‌های برنامه دریافت شوند. اینکار را توسط دستور npm install و یا دستور yarn (در صورتیکه yarn را از قبل بر روی سیستم خود نصب کرده‌اید) انجام خواهیم داد:
npm install
به این ترتیب تمامی وابستگی‌های پروژه درون پوشه‌ی node_module تشکیل خواهند شد. اکنون می‌توانیم با صدور دستور npm run dev پروژه را اجرا کنیم:

بررسی فایل‌های Vue
درون یک برنامه‌ی Vue واقعی، فایل‌هایی با پسوند vue. وجود دارند. این فایل شامل تمپلیت، کدها و همچنین استایل‌های یک کامپوننت می‌باشند. 
<template><div><!-- Write your HTML with Vue in here --></div></template><script>
    export default {
        // Write your Vue component logic here
    }</script><style scoped>
    /* Write your styles for the component in here */</style>

بنابراین درون فایلی با ساختار فوق، تمامی موارد مورد نیاز برای یک کامپوننت ویو را خواهیم داشت و به اصطلاح نیازی به context switching نخواهیم داشت؛ زیرا تمامی قسمت‌ها را به صورت یکجا در یک محل در اختیار داریم و به راحتی می‌توانیم تمرکز خود را بر روی کدها قرار دهیم. درون کامپوننت نیز می‌توانیم کامپوننت‌های موردنیاز را ایمپورت و از آن استفاده کنیم: 
import { New } from "./components/New.vue";
export default {
    components: {
        New
    }
}


Vue CLI 3
تا اینجا از نسخه‌ی پایدار Vue CLI استفاده کردیم. نسخه‌ی 3 آن هنوز در مرحله‌ی beta قرار دارد. در این نسخه امکانات و دستورات بیشتری اضافه شده‌است؛ از ایجاد یک پروژه ساده تا ایجاد یک پروژه مبتنی بر TypeScript. برای نصب و یا آپگرید می‌توانید از دستور زیر استفاده کنید:
npm install -g @vue/cli
اکنون می‌توانید با صادر کردن دستور vue --version، شماره نسخه‌ی آن را مشاهده نمائید:
3.0.0-beta.11


ایجاد یک پروژه جدید
برای ایجاد یک پروژه جدید می‌توانید دستور زیر را صادر کنید:
vue create my-project
همانطور که مشاهده می‌کنید در این نسخه بجای استفاده از دستور vue init، از vue create استفاده شده است. در اینحالت می‌توانید نوع ایجاد پروژه را تعیین کنید:
Vue CLI v3.0.0-beta.11
? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint)
  Manually select features
حالت پیش‌فرض، چنین ساختاری را برایتان ایجاد خواهد کرد:

بعد از طی کردن مراحل، می‌توانید قالب پروژه‌ی ایجاد شده را به صورت یک preset داشته باشید تا در پروژه‌های آینده مجبور نباشید مراحل قبل را طی کنید. این preset درون یک فایل JSON به صورت زیر ذخیره خواهد شد و حاوی اطلاعات زیر است:
{
  "useConfigFiles": true,
  "router": true,
  "vuex": true,
  "cssPreprocessor": "sass",
  "plugins": {
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-eslint": {
      "config": "airbnb",
      "lintOn": ["save", "commit"]
    }
  }
}

در حالت manually نیز می‌توانید گزینه‌های بیشتری را برای تعیین نوع قالب پروژه، انتخاب نمائید. به عنوان مثال می‌توان از TypeScript یا اینکه از lintter یا formatter خاصی برای کدها استفاده کرد:

در ادامه دیگر آپشن‌ها را نیز می‌توانید تعیین کرده و در نهایت به صورت یک قالب از پیش تعریف شده نیز پروژه را داشته باشید:


Zero-config Prototyping 
یکی از قابلیت‌های جالب Vue، امکان تهیه سریع prototype یا طرح اولیه می‌باشد. شاید اکثر اوقات نیاز داشته باشید یک ویژگی یا قابلیت خاص را با Vue تست کنید. در این موارد ممکن است از سایتی مانند CodePen استفاده کنید. اما توسط افزونه‌ی cli-service-global می‌توانید به صورت لوکال و بدون نیاز به راه‌اندازی یک پروژه‌ی جدید، کدهای موردنیاز را آزمایش کنید. فرض کنید می‌خواهیم تمپلیت زیر را قبل از افزودن آن به پروژه، مورد تست قرار دهیم:
<!-- MyCard.vue --><template><div class="card"><h1>Card Title</h1><p>Card content goes here. Make sure it's not Lorem.</p></div></template>
در این‌حالت می‌توانیم با نصب افزونه موردنظر، فایل فوق را به راحتی و بدون نیاز به راه‌اندازی یک پروژه جدید، تست کنیم:
npm install -g @vue/cli-service-global
اکنون می‌توانیم خروجی را با صدور فرمان زیر درون مرورگر مشاهده کنیم:
vue serve MyCard.vue
با صدرو فرمان فوق، فایل توسط افزونه‌ی عنوان شده، از طریق مرورگر قابل دسترسی می‌باشد:

خروجی:

‫پشتیبانی توکار از GDPR در ASP.NET Core 2.1

$
0
0
دیروز (25 ماه May سال 2018) اولین روز فعالسازی GDPR یا General Data Protection Regulation بود و به همین خاطر است که اگر به سرویس‌های مهم اینترنتی دقت کرده باشید، پر شده‌است از پیام‌هایی مانند «ما از کوکی استفاده می‌کنیم»، «ما اطلاعات شما را به این صورت ذخیره می‌کنیم» و امثال آن. همچنین تعداد زیادی از سرویس‌های اینترنتی نیز به کاربران خود پیام‌هایی را جهت تائید قوانین جدید رعایت حریم خصوصی آن‌ها ارسال کرده‌اند. برای مثال اگر این قوانین جدید را تائید نکنید، از دریافت بسیاری از خبرنامه‌ها محروم خواهید شد. این مورد نیز از بخش‌نامه‌ی اتحادیه‌ی اروپا نشات می‌گیرد که از روز جمعه ۲۵ می‌(۴ خرداد) تمامی شرکت‌ها، افراد، وب‌سایت‌ها و ارائه‌دهندگان خدمات آنلاین، موظف به رعایت آن هستند. موضوع بخش‌نامه قبل از هرچیزی، حفاظت از اطلاعات خصوصی کاربران است. نهادها و شرکت‌ها و وب‌سایت‌هایی که تا ۲۵ می‌۲۰۱۸ زمینه اجرای این بخش‌نامه را فراهم نکرده باشند، در خطر جریمه‌های سنگین هستند. بخش‌نامه جدید حریم خصوصی اطلاعات، تعیین می‌کند که چه میزان اطلاعاتی درباره‌ی هرکسی می‌تواند جمع‌آوری و بررسی شود، مورد پردازش قرار گیرد و البته تبدیل به پول شود. این بخش‌نامه حق تک‌تک کاربران، بر اطلاعات‌شان را تقویت می‌کند. کاربران حالا حق بیشتری بر اطلاعات‌شان، برای «پاک کردن» آن‌ها و «پس‌گرفتن» آن‌ها دارند. البته آن‌چه که احتمالا برای همه قابل رؤیت خواهد بود توضیحات مربوط به حفاظت از اطلاعات در وبسایت‌ها است. این توضیحات باید جزئی‌تر و دقیق‌تر و برای مخاطبان قابل فهم‌تر باشند و این به این معنا است که این توضیحات به‌مراتب طولانی‌تر خواهند شد.
در این بین اگر به قالب پیش‌فرض پروژه‌های MVC تولید شده‌ی توسط ASP.NET Core 2.1 نیز دقت کنید، پشتیبانی توکار از پیشنیازهای GDPR در آن لحاظ شده‌است؛ چه از لحاظ گوشزد کردن شرایط حریم خصوصی و پذیرش آن و چه از لحاظ «پاک کردن» و «پس گرفتن» اطلاعات شخصی.


قالب و کوکی پذیرش شرایط حریم خصوصی سایت (Cookie Consent)


اگر قالب پیش‌فرض یک پروژه‌ی ASP.NET Core 2.1 را اجرا کنید، تصویر فوق را که در آن نوار پذیرش شرایط حریم خصوصی سایت در بالای صفحه درج شده‌است، مشاهده خواهید کرد.
قالب جدید نوار پذیرش شرایط حریم خصوصی در مسیر Views\Shared\_CookieConsentPartial.cshtml واقع شده‌است و در فایل layout برنامه توسط tag helper جدید Partial، رندر و نمایش داده می‌شود:
<partial name="_CookieConsentPartial" />
در ابتدای این partial view، یک چنین کدهایی درج شده‌اند:
@using Microsoft.AspNetCore.Http.Features
@{
  var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
  var showBanner = !consentFeature?.CanTrack ?? false;
  var cookieString = consentFeature?.CreateConsentCookie();
}
بنابراین پذیرش شخص را در یک کوکی درج می‌کند و در دفعات بعدی بازدید او بر اساس این کوکی است که در مورد نمایش یا عدم نمایش این نوار پذیرش شرایط، تصمیم گیری خواهد شد. این کوکی نیز که تحت عنوان میان‌افزار CookiePolicy در سیستم مدیریت و پردازش می‌شود، به صورت زیر در فایل آغازین برنامه مدیریت می‌گردد:
الف) تنظیم نیاز به دریافت پذیرش
public void ConfigureServices(IServiceCollection services)
{
   services.Configure<CookiePolicyOptions>(options =>
   {
     // This lambda determines whether user consent for non-essential cookies is needed for a given request.
     options.CheckConsentNeeded = context => true;
     options.MinimumSameSitePolicy = SameSiteMode.None;
   });
ب) فعالسازی میان‌افزار مدیریت کوکی پذیرش شرایط حریم خصوصی
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   // ...
   app.UseCookiePolicy();


دادن حق فراموش شدن به کاربران

علاوه بر Cookie Consent فوق که در یک قالب ابتدایی MVC نیز درج شده‌است، در قالب پروژه‌های ASP.NET Core Identity، دو گزینه‌ی جدید دریافت اطلاعات شخصی و همچنین حذف اکانت (دادن حق فراموشی به کاربران) نیز پیش‌بینی شده‌است: PersonalData.cshtml


البته این صفحه جزو بسته‌ی جدید Microsoft.AspNetCore.Identity.UI است که به همراه ASP.NET Core 2.1 ارائه می‌شود:
 dotnet add package Microsoft.AspNetCore.Identity.UI --version 2.1.0-rc1-final
در این بسته تمام کدها و صفحات مخصوص Identity به داخل یک Class library جدید منتقل شده‌اندو دیگر جزو قالب پروژه‌ی «dotnet new mvc --auth Individual» یا همان تنظیم اعتبارسنجی به individual user accounts نیستند و باید به صورت جداگانه دریافت و تنظیم شوند (اختیاری است).

‫مراحل تنظیم Let's Encrypt در IIS

$
0
0
روزگاری دریافت مجوزهای SSL، گران و سخت بود. برای رفع این مشکلات مؤسسه‌هایی مانند Let's Encryptپدیدار شده‌اند که مجوزهای SSL رایگانی را برای سایت‌های اینترنتی صادر می‌کنند. دسترسی به سرویس آن‌ها از طریق API ارائه شده‌ی آن، بسیار ساده بوده، کار با آن رایگان است و نیاز به مجوز خاصی ندارد. فقط باید دقت داشت که گواهینامه‌های Let's Encrypt دو ماهه هستند و وب‌سرور سایت شما باید اجازه‌ی دسترسی به محل ویژه‌ای را که جهت تعیین اعتبار دومین درخواستی ایجاد می‌شود، صادر کند. البته درخواست گواهی مجدد و تمدید آن در هر زمانی، حتی اگر پیش از انقضای آن باشد، مسیر است و از این لحاظ محدودیتی ندارد. در ادامه نحوه‌ی کار با این سرویس را در ویندوزهای سرور بررسی خواهیم کرد.


دریافت برنامه‌ی win-acme

برنامه‌ی win-acme کار دریافت، نصب و تنظیم به روز رسانی خودکار مجوزهای Let’s Encrypt را در ویندوز بسیار ساده کرده‌است و تقریبا به برنامه‌ی استاندارد انجام اینکار تبدیل شده‌است. این برنامه، انجام مراحل زیر را خودکار کرده‌است:
- اسکن IIS برای یافتن bindings و نام سایت‌ها
- اتصال به Let’s Encrypt certificate authority و دریافت مجوزهای لازم
- درج خودکار مجوزهای دریافتی در Windows certificate store
-ایجاد HTTPS binding خودکار در IIS
- استفاده از Windows Task Scheduler‌، جهت ایجاد وظیفه‌ی به روز رسانی خودکار مجوزهای درخواست شده

به همین جهت پیش از هر کاری نیاز است این برنامه را دریافت کنید:
https://github.com/PKISharp/win-acme/releases

این برنامه از دات نت نگارش 4.6.2 استفاده می‌کند. بنابراین نیاز است این نگارش و یا ترجیحا آخرین نگارش دات نت فریم ورکرا بر روی سرور نصب کنید.


آماده سازی برنامه‌ی ASP.NET جهت دریافت مجوزهای Let’s Encrypt

سرور Let’s Encrypt، در حین صدور مجوز برای سایت شما نیاز دارد بررسی کند که آیا شما واقعا صاحب همان دومین هستید یا خیر. به همین جهت مسیر
/.well-known/acme-challenge/id
را بر روی سرور شما بررسی خواهد کرد (بنابراین سرور شما باید به اینترنت متصل بوده و همچنین مجوز دسترسی به این مسیر را عمومی کند). برنامه‌ی win-acme این id را از سرور Let’s Encrypt به صورت خودکار دریافت کرده و فایلی را در مسیر یاد شده ایجاد می‌کند. سپس سرور Let’s Encrypt یکبار این مسیر را خواهد خواند. مشکل اینجا است که دسترسی به این فایل بدون پسوند در برنامه‌های MVC به صورت پیش‌فرض مسیر نیست و نیازی به تنظیمات خاصی دارد:
روش انجام اینکار در ASP.NET Core به صورت زیر است:
[HttpGet("/.well-known/acme-challenge/{id}")]
public IActionResult LetsEncrypt(string id, [FromServices] IHostingEnvironment env)
{
   id = Path.GetFileName(id); // security cleaning
   var file = Path.Combine(env.ContentRootPath, ".well-known", "acme-challenge", id);
   return PhysicalFile(file, "text/plain");
}
این اکشن متد را در هر کنترلری قرار دهید، تفاوتی نمی‌کند و کار خواهد کرد؛ چون attribute routing آن مستقل از محل قرارگیری آن است.
در MVC 5x پارامتر env را حذف و بجای آن از Server.MapPath و در آخر از return File استفاده کنید.
[Route("/.well-known/acme-challenge/{id}")]
public ActionResult LetsEncrypt(string id)
{
   id = Path.GetFileName(id); // security cleaning
   var file = Path.Combine(Server.MapPath("~/.well-known/acme-challenge"), id);
   return File(file, "text/plain", id);
}
اگر این مرحله را تنظیم نکنید، در وسط کار دریافت مجوز توسط برنامه‌ی win-acme، به علت اینکه مشخص نیست آیا شما صاحب دامنه هستید یا خیر، خطایی را دریافت کرده و برنامه متوقف می‌شود.


آماده سازی IIS برای دریافت مجوزهای Let’s Encrypt

ابتدا به قسمت Edit bindings وب سایتی که قرار است مجوز را دریافت کند مراجعه کرده:


و سپس bindings مناسبی را ایجاد کنید (از نوع HTTP و نه HTTPS):


برای مثال اگر سایت شما قرار است توسط آدرس‌های www.dotnettips.info و dotnettips.info در دسترس باشد، نیاز است دو binding را در اینجا ثبت کنید. برای تمام موارد ثبت شده هم این تنظیمات را مدنظر داشته باشید:
Type:http
Port:80
IP address:All Unassigned
برنامه‌ی win-acme بر اساس این HTTP Bindings است که معادل‌های متناظر HTTPS آن‌ها را به صورت خودکار ثبت و تنظیم می‌کند.


اجرای برنامه‌ی win-acme بر روی ویندوز سرور 2008

IISهای 8 به بعد (و یا ویندوز سرور 2012 به بعد) دارای ویژگی هستند به نام Server Name Indication و یا SNI که اجاز می‌دهند بتوان چندین مجوز SSL را بر روی یک IP تنظیم کرد.


در اینجا به ازای هر Bindings تعریف شده‌ی در قسمت قبل، یک مجوز Let’s Encrypt دریافت خواهد شد. اما چون ویندوز سرور 2008 به همراه IIS 7.5 است، فاقد ویژگی SNI است. به همین جهت در حالت عادی برای مثال فقط برای www.dotnettips.info مجوزی را دریافت می‌کنید و اگر کاربر به آدرس dotnettips.info مراجعه کند، دیگر نمی‌تواند به سایت وارد شود و پیام غیرمعتبر بودن مجوز SSL را مشاهده خواهد کرد.
برنامه‌ی win-acme برای رفع این مشکل، از ویژگی خاصی به نام «SAN certificate» پشتیبانی می‌کند.
به این ترتیب با ویندوز سرور 2008 هم می‌توان دامنه‌ی اصلی و زیر دامنه‌های تعریف شده را نیز پوشش داد و سایت به این ترتیب بدون مشکل کار خواهد کرد. مراحل تنظیم SAN توسط برنامه‌ی win-acme به این صورت است:

ابتدا که برنامه‌ی win-acme را با دسترسی admin اجرا می‌کنید، چنین منویی نمایش داده می‌شود:
 N: Create new certificate
 M: Create new certificate with advanced options
 L: List scheduled renewals
 R: Renew scheduled
 S: Renew specific
 A: Renew *all*
 V: Revoke certificate
 C: Cancel scheduled renewal
 X: Cancel *all* scheduled renewals
 Q: Quit
گزینه‌ی N یا ایجاد مجوز جدید را انتخاب کنید.
سپس منوی بعدی را نمایش می‌دهد:
 [INFO] Running in Simple mode

 1: Single binding of an IIS site
 2: SAN certificate for all bindings of an IIS site
 3: SAN certificate for all bindings of multiple IIS sites
 4: Manually input host names
 C: Cancel
در این حالت برای ویندوز سرور 2008، فقط و فقط گزینه‌ی 2 را انتخاب کنید.
سپس لیست سایت‌های نصب شده‌ی در IIS را نمایش می‌دهد:
 1: Default Web Site
 C: Cancel

 Choose site: 1
در اینجا برای مثال شماره‌ی 1 یا هر شماره‌ی دیگر متناظر با وب سایت مدنظر را انتخاب کنید.
در ادامه منوی زیر را نمایش می‌دهد:
 * www.dotnettips.info
 * dotnettips.info

 Press enter to include all listed hosts, or type a comma-separated lists of exclusions:
لیستی را که در اینجا مشاهده می‌کنید، همان Bindings است که پیشتر ایجاد کردیم. عنوان می‌کند که برای کدامیک از این‌ها نیاز است مجوز دریافت و نصب شود. کلید enter را فشار دهید تا برای تمام آن‌ها اینکار صورت گیرد.
و ... همین! پس از آن کار دریافت، نصب و به روز رسانی Bindings در IIS به صورت خودکار انجام خواهد شد. اکنون اگر به قسمت Binding سایت مراجعه کنید، این تنظیمات خودکار جدید را مشاهده خواهید کرد:


اگر به لاگ نصب مجوزها دقت کنید این دو سطر نیز در انتهای آن ذکر می‌شوند:
 [INFO] Adding renewal for Default Web Site
 [INFO] Next renewal scheduled at 2018/7/21 4:19:20 AM
علت اینجا است که مجوزهای Let’s Encrypt طول عمر کمی دارند و در صورت به روز نشدن مداوم، کاربران دیگر قادر به مرور سایت نخواهند بود. به همین جهت این برنامه یک Scheduled Task ویندوز را نیز جهت به روز رسانی خودکار این مجوزها ایجاد و تنظیم می‌کند.


اجرای برنامه‌ی win-acme بر روی ویندوزهای سرور 2012 به بعد

چون IIS ویندوزهای سرور 2012 به بعد، از نصب و فعالسازی بیش از یک مجوز SSL به ازای یک IP به صورت توکار تحت عنوان ویژگی SNI پشتیبانی می‌کنند، مراحل انجام آن ساده‌تر هستند.
ابتدا که برنامه‌ی win-acme را با دسترسی admin اجرا می‌کنید، چنین منویی نمایش داده می‌شود:
 N: Create new certificate
 M: Create new certificate with advanced options
 L: List scheduled renewals
 R: Renew scheduled
 S: Renew specific
 A: Renew *all*
 V: Revoke certificate
 C: Cancel scheduled renewal
 X: Cancel *all* scheduled renewals
 Q: Quit
گزینه‌ی N یا ایجاد مجوز جدید را انتخاب کنید.
سپس منوی بعدی را نمایش می‌دهد:
 [INFO] Running in Simple mode

 1: Single binding of an IIS site
 2: SAN certificate for all bindings of an IIS site
 3: SAN certificate for all bindings of multiple IIS sites
 4: Manually input host names
 C: Cancel
در این حالت گزینه‌ی 4 را انتخاب کنید (با فرض اینکه از IIS 8.0 به بعد استفاده می‌کنید).
سپس از شما درخواست می‌کند که لیست دامنه و زیر دامنه‌هایی را که قرار است برای آن‌ها مجوز SSL صادر شوند، به صورت لیست جدا شده‌ی توسط کاما، وارد کنید:
 Enter comma-separated list of host names, starting with the primary one: dotnettips.info, www.dotnettips.info
در ادامه لیست وب سایت‌های ثبت شده‌ی در IIS را نمایش می‌دهد:
1: Default Web Site
2: mysiteName
Choose site to create new bindings: 1
در اینجا شماره‌ی سایتی را که می‌خواهید برای آن مجوز صادر شود، انتخاب کنید.
و ... همین! پس از آن مجوزهای SSL درخواستی، دریافت، نصب و تنظیم خواهند شد. همچنین یک Scheduled Task هم برای به روز رسانی خودکار آن تنظیم می‌شود.
Viewing all 1980 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>