اشتراک‌ها
شناخت NET Core, NETStandard, .NET Core applications and ASP.NET Core

As anyone in the .NET community who hasn't been living under a rock will know, there's a lot of exciting things happening with .NET at the moment with the announcement of the open source, cross platform, .NET Core. However, partly due to the very open nature of its evolution, there's been a whole host of names associated with its development - vNext, ASP.NET 5, ASP.NET Core, .NET generations etc.

In this post I'm going to try and clarify some of the naming and terminology surrounding the evolution of the .NET framework. I'll discuss some of the challenges the latest iteration is attempting to deal with and how the latest developments aim to address these.

This is really for those that have seen some of the big announcements but aren't sure about the intricacies of this new framework and how it relates to the existing ecosystem, which was my situation before I really started digging into it all properly!

Hopefully by the end of this article you'll have a clearer grasp of the latest in .NET! 

شناخت NET Core, NETStandard, .NET Core applications and ASP.NET Core
اشتراک‌ها
مستندات ASP.NET 5
    Getting Started
        Installing ASP.NET 5 On Windows
        Installing ASP.NET 5 On Mac OS X
        Installing ASP.NET 5 On Linux
        Choosing the Right .NET For You on the Server
    Tutorials
        Your First ASP.NET 5 Application Using Visual Studio
        Your First ASP.NET 5 Application on a Mac
        Publish to an Azure Web App using Visual Studio
    Conceptual Overview
        Introduction to ASP.NET 5
        Introducing .NET Core
        DNX Overview
        Understanding ASP.NET 5 Web Apps
    Fundamentals
        Working with Static Files
        Routing
        Configuration
        Dependency Injection
        Diagnostics
        Working with Multiple Environments
        OWIN
    .NET Execution Environment (DNX)
        DNX Overview
        Creating a Cross-Platform Console App with DNX
        Working with DNX Projects
        Using Commands
    Publishing and Deployment
        Publish to a Docker Image
    Client-Side Development
        Grunt and Gulp: Task Runners
        Manage Client-Side Packages with Bower
        Building Beautiful, Responsive Sites with Bootstrap
        Knockout.js MVVM Framework
        Styling Applications with Less, Sass, and Font Awesome
    Security
        Enabling authenication using external providers
        Account Confirmation and Password Recovery with ASP.NET Identity
        Two-factor authenication with SMS using ASP.NET Identity
        Data Protection
    Extensibility
        Writing Middleware
مستندات ASP.NET 5
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت یازدهم - کار با فرم‌ها - قسمت دوم
مجموعه‌ای از اعتبارسنج‌های سفارشی Cross Field
buildForm(){
 this.form = _fb.group({
 field1: [null, Validators.compose([Validators.required, CorssFieldValidators.greaterThan('field2', 'فیلد دو')])],
 field2: [null,  CorssFieldValidators.requiredIf('field1', 20)],
 password: [null, Validators.required],
 confirmPassword: [null, Validators.compose([Validators.required, CorssFieldValidators.equal('password')])]
 })
}


export class CrossFieldValidators {

  static greaterThanEqual(controlName: string, label: string): ValidatorFn {
    return this.is(controlName, label, 'greaterThanEqual');
  }

  static greaterThan(controlName: string, label: string): ValidatorFn {
    return this.is(controlName, label, 'greaterThan');
  }

  static lessThan(controlName: string, label: string): ValidatorFn {
    return this.is(controlName, label, 'lessThan');
  }

  static lessThanEqual(controlName: string, label: string): ValidatorFn {
    return this.is(controlName, label, 'lessThanEqual');
  }

  static equal(controlName: string, label: string): ValidatorFn {
    return this.is(controlName, label, 'equal');
  }

  static requiredIf(
    controlName: string,
    value: any
  ): ValidatorFn {
    let subscribed = false;
    const validatorFn = (control: AbstractControl): { [key: string]: any } => {
      if (!control.root || !(control.root as FormGroup).controls) return null;

      const dependentControl = control.root.get(controlName);
      if (!subscribed) {
        subscribed = true;
        dependentControl.valueChanges.subscribe(() => {
          control.updateValueAndValidity();
          control.markAsTouched();
        });
      }

      const requiredFn = Validators.required;
      return dependentControl.value === value ? requiredFn(control) : null;
    };
    return validatorFn;
  }

  private static is(
    controlName: string,
    label: string,
    operator:
      | 'equal'
      | 'greaterThan'
      | 'greaterThanEqual'
      | 'lessThan'
      | 'lessThanEqual'
  ): ValidatorFn {
    let subscribed = false;
    const validatorFn = (control: AbstractControl): { [key: string]: any } => {
      const value = control.value;
      if (value === null) return null;

      if (!control.root || !(control.root as FormGroup).controls) return null;

      const dependentControl = control.root.get(controlName);
      if (!subscribed) {
        subscribed = true;
        dependentControl.valueChanges.subscribe(() => {
          control.updateValueAndValidity({ emitEvent: operator === 'equal' });
          control.markAsTouched();
        });
      }

      const dependentValue = dependentControl.value;
      if (dependentValue === null) return null;

      let isInvalid = false;
      switch (operator) {
        case 'equal':
          isInvalid = dependentValue !== value;
          break;
        case 'greaterThan':
          isInvalid = parseFloat(dependentValue) >= parseFloat(value);
          break;
        case 'greaterThanEqual':
          isInvalid = parseFloat(dependentValue) > parseFloat(value);
          break;
        case 'lessThan':
          isInvalid = parseFloat(dependentValue) <= parseFloat(value);
          break;
        case 'lessThanEqual':
          isInvalid = parseFloat(dependentValue) < parseFloat(value);
          break;
        default:
          throw 'invalid operator!';
      }
      return isInvalid ? { [operator]: { label } } : null;
    };

    return validatorFn;
  }
}


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

کوکی‌ها به اندازه‌ی خود اینترنت قدیمی هستند و تا سال‌ها، تنها گزینه‌ی شخصی سازی تجربه‌ی کاربری در وب و انتقال آن از یک صفحه به صفحه‌ی دیگری به‌شمار می‌آمدند؛ به این نوع کوکی‌ها، First-party cookies هم می‌گویند و توسط خود سایت ارائه دهنده‌ی محتوا و برنامه‌ها، تنظیم می‌شوند. در مقابل آن، third-party cookies یا کوکی‌های ثالث هم وجود دارند که از طریق دومین دیگری بجز دومین اصلی برنامه، ارائه و تنظیم می‌شوند؛ به همین جهت به آن‌ها Cross-site cookies هم می‌گویند. یکی از اهداف کوکی‌های ثالث، ردیابی فعالیت کاربران در بین سایت‌های مختلف است و این روزها به علت سوء استفاده‌های زیادی که از آن‌ها می‌شوند،‌ یکی از وسایل به اشتراک‌گذاری و جمع آوری اطلاعات خصوصی کاربران شده‌اند.

البته کوکی‌های ثالث، کاربردهای مفیدی هم دارند؛ مانند امکان پیاده سازی لاگین و اعتبارسنجی یکپارچه‌ی بین چندین برنامه که به آن SSO یا Single Sign On هم گفته می‌شود؛ نمونه‌ی آن، استفاده از Identity server و یا OpenId dict در دنیای دات‌نت است.

اما ... در کل به علت مشکلات یاد شده، اکثر مرورگرها تصمیم به عدم پذیرش و پردازش آن‌ها گرفته‌اند. برای مثال مرورگر Safari سال‌ها است که اینگونه کوکی‌ها را بلاک می‌کند و یا مرورگر فایرفاکس، کوکی‌های ثالث ردیاب‌ها را بلاک می‌کند و ... مرورگر کروم نیز تصمیم گرفته‌است، تا پایان سال 2024، به این جمع محلق شود. هرچند اخیرا اعلام کرده‌اند، بجای اینکه مرورگر کروم، راسا این کار را انجام دهد، قرار است امکان انتخاب این گزینه به خود کاربر واگذار شود (چون ... خود گوگل از این نوع کوکی‌ها منتفع است!). البته این تصمیم، به‌نظر W3C خوش نیامده و اعلام کرده‌اند که این نوع کوکی‌ها باید بروند!

روش آزمایش برنامه‌ها برای بررسی تاثیر ممنوعیت کوکی‌های ثالث

برای اینکه پیش از موعد، امکان آزمایش برنامه‌ی خود را داشته باشید و بتوانید تاثیر ممنوعیت بکارگیری کوکی‌های ثالث را بررسی کنید، در مرورگر کروم به آدرس زیر مراجعه کرده:

chrome://flags/#test-third-party-cookie-phaseout

و سپس این گزینه را فعال کنید و یا روش دوم فعالسازی آن، اجرای مرورگر کروم با سوئیچ test-third-party-cookie-phaseout-- از طریق خط فرمان است.

کدام برنامه‌ها با ممنوعیت کوکی‌های ثالث مشکل پیدا می‌کنند؟

اگر برنامه‌ی SSO شما، مبتنی بر اعتبارسنجی یکپارچه‌ی از نوع implicit flow است، حتما مشکل پیدا می‌کنید. در این حالت بهتر است به نوع امن‌تر authorization flow + PKCE‌ مهاجرت کنید.

علت مسدود شدن نوع اعتبارسنجی و احراز هویت implicit flow که در برنامه‌های تک صفحه‌ای وب (SPA/single-page apps) مرسوم است، شباهت بسیار زیاد آن به کاری است که ردیاب‌های اینترنتی انجام می‌دهند. در این‌حالت، سایت‌های ثالث، یک iframe مخفی را در پشت صحنه، درون سایت جاری باز کرده و توسط آن شروع به استفاده‌ی از امکانات مرتبط با کوکی‌ها می‌کنند. این الگو دقیقا همان کاری است که توسط implicit flow هم انجام می‌شود. بنابراین مرورگری که کوکی‌های ثالث از این دست را مسدود می‌کند، قابلیت اعتبارسنجی یکپارچه‌ی برنامه‌های SPA را هم غیرفعال خواهد کرد.

مطالب
نگاهی به تاریخچه‌ی زبان #C
این قهرمان ما از سال ۲۰۰۲ سفر خودش را همراه با Visual Studio 2002 شروع کرد و تا الان (۲۰۲۳) حدود ۱۱ بار آپدیت‌های جدید و عالی‌ای را ارائه داده‌است. در اوایل کار، زبانی شبیه به Java بود و صرفا نسبت به زبان‌های سطح پایین، تنها چیزی که اضافه داشت، بحث شی‌ءگرایی بود، اما در ادامه وارد عصر‌های مختلفی شد که بد نیست نگاهی به آن‌ها داشته باشیم. 

عصر نخستین: تبدیل شدن به یک زبان قابل قبول
C# 1.0, C# 1.2, C# 2.0
در این عصر، زبانی را مشاهده می‌کنیم که تقریبا مانند بقیه‌ی زبان‌های C-Base هست و تفاوت چندانی نمی‌کند. می‌شود گفت اینجا کار کردن با انواع داده‌ها نسبت به بقیه زبان‌ها آسان‌تر است. با قابلیت‌های شی‌ءگرایی شروع کرده و در ادامه ویژگی‌های دیگری را هم در ورژن‌های بعدی خود ارائه داد.

عصر دوم: اضافه شدن امکانات منحصر بفرد
C# 3.0 , C# 4.0, C# 5.0
حدود سال ۲۰۰۷، قهرمان ما تصمیم گرفت امکانات منحصر بفردی را ارائه دهد که این زبان را نسبت به بقیه‌ی هم ردیف‌های خودش متمایز کند. این امکانات همراه با NET Framework version 3.5 و Visual Studio 2008 وارد بازار شدند. امکانات نام آشنایی از قبیل Lambda expression ها،Object and collection initializer‌ها و ... در این ورژن به سی‌شارپ اضافه شدند.

عصر سوم: باز نویسی کامل کامپایلر با سی‌شارپ (Roslyn)
سال ۲۰۱۵ سی‌شارپ ۶ همراه با Visual Studio 2015 وارد بازار شد. اینبار سی‌شارپ شروع به اعمال تغییراتی کرد که عمدتا با ذهنیت کد تمیز و ساده همراه بود. از جمله‌ی این تغییرات مهم، بازنویسی کامل کامپایلر، با خود زبان سی‌شارپ بود.

عصر چهارم: رضایت طرفداران کد تمیز و ساده
C# 7.0  , C# 7.1 , C# 7.2, C# 7.3
شروع تغییرات کوچک، در ورژن ۶ سی‌شارپ بود؛ ولی از ورژن ۷ به بعد، مایکروسافت تمرکز خیلی بیشتری را بر روی این کار گذاشت و تغییراتش همگی دارای یک هدف مهم بودند. آسان و تمیز بودن کدها؛ امکاناتی از قبلی tuple,out,ref و ... از جمله این تغییرات بودند.

عصر پنجم: دنیای Cross-Platform، خداحافظی با NullReferenceException و تلاش برای شبیه شدن به زبان‌های اسکریپت نویسی
سال‌ها برنامه نویس‌ها با خطای NullReferenceException دست و پنجه نرم میکردند، ولی حالا با استفاده‌ی درست از قابلیت Nullable reference type‌ها میشد تا حد قابل قبولی جلوی این اتفاق را گرفت. در ادامه تغییرات به سمتی می‌رفت که زبان سی‌شارپ را شبیه به یک زبان اسکریپت نویسی کرده بود. حالا میشد بدون تعریف کلاس و متد خاصی، دستور ساده‌ای را اجرا کرد. همچنین قابلیت‌هایی که در pattern matching به سی‌شارپ اضافه شد، باعث ساده‌تر و قابل فهم‌تر شدن سی‌شارپ میشد. 


نقشه‌ی راه تصویری پیشرفت سی‌شارپ

مطالب
حذف کاراکتر های ناخواسته توسط Recursive CTE قسمت دوم
مقدمه
در قسمت پیشین نشان داده شد که چگونه کاراکتر‌های خارج از رنج حروف الفبای انگلیسی از عبارات موجود در یک جدول حذف شدند.
اکنون شرایط کمی تغییر کرده است کاراکترهای ناخواسته در قالب یک مجموعه (جدول) به ما ارائه داده می‌شوند. ما بایستی تمام کاراکترهای داده شده را از عبارات (موجود در جدول) در صورت تطابق حذف کنیم.

جدول کاراکترهای ناخواسته Unwanted و جدول داده‌ها Data نامگذاری شده اند.
CREATE TABLE Data 
(
id INTEGER NOT NULL PRIMARY KEY IDENTITY,
data VARCHAR(50) NOT NULL
);

INSERT INTO Data
VALUES ('~!hasan @#$%^&*(reza)[ali]^^^^^^^^'),
('(Ja[][][]va~!@#$*d-mohammad)'),
('Mohammad'), ('Maryam');

CREATE TABLE Unwanted
(
id INT NOT NULL PRIMARY KEY IDENTITY, 
chars CHAR(1) NOT NULL UNIQUE
);

INSERT Unwanted 
VALUES ('~'),('!'),('@'),('#'),('$'),('%'),
('['),(']'),('^'),('&'),('*');
الگوریتم
قبل از هر چیزی فرض میگیریم که داده‌ها ستون Id جدول Unwanted کاملا متوالی نیستند و gap بین مقادیر id وجود دارد. پس برای داشتن مقادیر متوالی از تابع row_number استفاده شده است.
بعد از آن باید عباراتی که حداقل با یک کاراکتر ناخواسته تطابق پیدا میکنند انتخاب شوند. و عملیات پاکسازی روی این عبارات به تعداد کاراکترهای موجود در جدول unwanted انجام می‌شود. یعنی در مرحله اول شمارنده برابر است با تعداد کاراکترهای ناخواسته سپس در هر فراخوانی یک مقدار از این شمارنده کم شده تا اینکه به 0 برسد در اینجا کار به اتمام می‌رسد.
بعد از پایان یافتن فراخوانی‌های query بازگشتی باید سطرهایی که شمارنده آنها برابر با 0 است انتخاب شده و عباراتی که در مرحله اول به دلیل عدم  تطابق انتخاب نشدن بایستی به نتیجه اضافه شوند. این کار را توسط Union all انجام شده است.
WITH pre AS
(
SELECT ROW_NUMBER() OVER(ORDER BY id) AS id, chars
  FROM Unwanted
),
 cte AS
(
SELECT  data.id,
        nbr,
        CAST(Data AS VARCHAR(50)) AS Data
  FROM Data
       CROSS JOIN (SELECT COUNT(*) AS nbr FROM Unwanted)t
 WHERE EXISTS
       (SELECT *
          FROM Unwanted
         WHERE Data LIKE '%#' + chars + '%' ESCAPE '#')
 
 UNION ALL
  
 SELECT C.id,
        nbr - 1,
        CAST(REPLACE(C.data, U.chars, '') AS VARCHAR(50))
   FROM cte AS C
        INNER JOIN pre U
        ON C.nbr = U.id
  WHERE nbr > 0
)
SELECT data
  FROM cte
 WHERE nbr = 0
 
 UNION ALL
  
SELECT data
  FROM Data
 WHERE NOT EXISTS
       (SELECT id           
          FROM cte
         WHERE cte.id = data.id);

مطالب
لینک‌های هفته آخر آذر

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


امنیت


Visual Studio


ASP. Net


طراحی وب


PHP

  • Aptana PHP 1.0 منتشر شد (اگر قبلا این IDE بسیار قابل توجه را دریافت کرده بودید فقط کافی است به منوی aptana و گزینه my aptana مراجعه کرده و از قسمت plugins ، این پلاگین 18 مگابایتی را دریافت کنید.)

اس‌کیوال سرور


سی شارپ


عمومی دات نت


ویندوز


متفرقه


مسیرراه‌ها
MDX Query
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دهم - کار با فرم‌ها - قسمت اول
هر برنامه‌ی وبی، نیاز به کار با فرم‌های وب را دارد و به همین جهت، AngularJS 2.0 به همراه دو نوع از فرم‌ها است: فرم‌های مبتنی بر قالب‌ها و فرم‌های مبتنی بر مدل‌ها.
کار با فرم‌های مبتنی بر قالب‌ها ساده‌تر است؛ اما کنترل کمتری را بر روی مباحث اعتبارسنجی داده‌های ورودی توسط کاربر، در اختیار ما قرار می‌دهند. اما فرم‌های مبتنی بر مدل‌ها هر چند به همراه اندکی کدنویسی بیشتر هستند، اما کنترل کاملی را جهت اعتبارسنجی ورودی‌های کاربران، ارائه می‌دهند. در این قسمت فرم‌های مبتنی بر قالب‌ها (Template-driven forms) را بررسی می‌کنیم.


ساخت فرم مبتنی بر قالب‌های ثبت یک محصول جدید

در ادامه‌ی مثال این سری، می‌خواهیم به کاربران، امکان ثبت اطلاعات یک محصول جدید را نیز بدهیم. به همین جهت فایل‌های جدید product-form.component.ts و product-form.component.html را به پوشه‌ی App\products برنامه اضافه می‌کنیم (جهت تعریف کامپوننت فرم جدید به همراه قالب HTML آن).
الف) محتوای کامل product-form.component.html
<form #f="ngForm" (ngSubmit)="onSubmit(f.form)">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">
                Add Product
            </h3>
        </div>
        <div class="panel-body form-horizontal">
            <div class="form-group">
                <label for="productName" class="col col-md-2 control-label">Name</label>
                <div class="controls col col-md-10">
                    <input ngControl="productName" id="productName" required
                           #productName="ngForm"
                           (change)="log(productName)"
                           minlength="3"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.productName"/>
                    <div *ngIf="productName.touched && productName.errors">
                        <label class="text-danger" *ngIf="productName.errors.required">
                            Name is required.
                        </label>
                        <label class="text-danger" *ngIf="productName.errors.minlength">
                            Name should be minimum {{ productName.errors.minlength.requiredLength }} characters.
                        </label>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label for="productCode" class="col col-md-2 control-label">Code</label>
                <div class="controls col col-md-10">
                    <input ngControl="productCode" id="productCode" required
                           #productCode="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.productCode"/>
                    <label class="text-danger" *ngIf="productCode.touched && !productCode.valid">
                        Code is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="releaseDate" class="col col-md-2 control-label">Release Date</label>
                <div class="controls col col-md-10">
                    <input ngControl="releaseDate" id="releaseDate" required
                           #releaseDate="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.releaseDate"/>
                    <label class="text-danger" *ngIf="releaseDate.touched && !releaseDate.valid">
                        Release Date is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="price" class="col col-md-2 control-label">Price</label>
                <div class="controls col col-md-10">
                    <input ngControl="price" id="price" required
                           #price="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.price"/>
                    <label class="text-danger" *ngIf="price.touched && !price.valid">
                        Price is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="description" class="col col-md-2 control-label">Description</label>
                <div class="controls col col-md-10">
                    <textarea ngControl="description" id="description" required
                              #description="ngForm"
                              rows="10" type="text" class="form-control"
                              [(ngModel)]="productModel.description"></textarea>
                    <label class="text-danger" *ngIf="description.touched && !description.valid">
                        Description is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="imageUrl" class="col col-md-2 control-label">Image</label>
                <div class="controls col col-md-10">
                    <input ngControl="imageUrl" id="imageUrl" required
                           #imageUrl="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.imageUrl"/>
                    <label class="text-danger" *ngIf="imageUrl.touched && !imageUrl.valid">
                        Image is required.
                    </label>
                </div>
            </div>
        </div>
        <footer class="panel-footer">
            <button [disabled]="!f.valid"
                    type="submit" class="btn btn-primary">
                Submit
            </button>
        </footer>
    </div>
</form>

ب) محتوای کامل product-form.component.ts
import { Component } from 'angular2/core';
import { Router } from 'angular2/router';
import { IProduct } from './product';
import { ProductService } from './product.service';
 
@Component({
    //selector: 'product-form',
    templateUrl: 'app/products/product-form.component.html'
})
export class ProductFormComponent {
 
    productModel = <IProduct>{}; // creates an empty object of an interface
 
    constructor(private _productService: ProductService, private _router: Router) { }
 
    log(productName): void {
        console.log(productName);
    }
 
    onSubmit(form): void {
        console.log(form);
        console.log(this.productModel);
 
        this._productService.addProduct(this.productModel)
            .subscribe((product: IProduct) => {
                console.log(`ID: ${product.productId}`);
                this._router.navigate(['Products']);
            });
    }
}

اکنون ریز جزئیات و تغییرات این دو فایل را قدم به قدم بررسی خواهیم کرد.

تا اینجا در فایل product-form.component.html یک فرم ساده‌ی HTML ایی مبتنی بر بوت استرپ 3 را تهیه کرده‌ایم. نکات ابتدایی آن، دقیقا مطابق است با مستندات بوت استرپ 3؛ از لحاظ تعریف form-horizontal و سپس ایجاد یک div با کلاس form-group و قرار دادن المان‌هایی با کلاس‌های form-control در آن. همچنین برچسب‌های تعریف شده‌ی با ویژگی for، در این المان‌ها، جهت بالارفتن دسترسی پذیری به عناصر فرم، اضافه شده‌اند. این مراحل در مورد تمام فرم‌های استاندارد وب صادق هستند و نکته‌ی جدیدی ندارند.

در ادامه تعاریف AngularJS 2.0 را به این فرم اضافه کرد‌ه‌ایم. در اینجا هر کدام از المان‌های ورودی، تبدیل به Controlهای AngularJS 2.0 شده‌اند. کلاس Control، خواص ویژه‌ای را در اختیار ما قرار می‌دهد. برای مثال value یا مقدار این المان چیست؟ وضعیت touched و untouched آن چیست؟ (آیا کاربر فوکوس را به آن منتقل کرده‌است یا خیر؟) آیا dirty است؟ (مقدار آن تغییر کرده‌است؟) و یا شاید هم pristine است؟ (مقدار آن تغییری نکرده‌است). علاوه بر این‌ها دارای خاصیت valid نیز می‌باشد (آیا اعتبارسنجی آن موفقیت آمیز است؟)؛ به همراه خاصیت errors که مشکلات اعتبارسنجی موجود را باز می‌گرداند.
<div class="form-group">
    <label for="description" class="col col-md-2 control-label">Description</label>
    <div class="controls col col-md-10">
        <textarea ngControl="description" id="description" required
                  #description="ngForm"
                  rows="10" type="text" class="form-control"
                  [(ngModel)]="productModel.description"></textarea>
        <label class="text-danger" *ngIf="description.touched && !description.valid">
            Description is required.
        </label>
    </div>
</div>
در اینجا کلاس مفید دیگری به نام ControlGroup نیز درنظر گرفته شده‌است. برای مثال هر فرم، یک ControlGroup است (گروهی متشکل از کنترل‌ها، در صفحه). البته می‌توان یک فرم بزرگ را به چندین ControlGroup نیز تقسیم کرد. تمام خواصی که برای کلاس Control ذکر شدند، در مورد کلاس ControlGroup نیز صادق هستند. با این تفاوت که این‌بار اگر به خاصیت valid آن مراجعه کردیم، یعنی تمام کنترل‌های قرار گرفته‌ی در آن گروه معتبر هستند و نه صرفا یک تک کنترل خاص. به همین ترتیب خاصیت errors نیز تمام خطاهای اعتبارسنجی یک گروه را باز می‌گرداند.
هر دو کلاس Control و ControlGroup از کلاس پایه‌ای به نام AbstractControl مشتق شده‌اند و این کلاس پایه است که خواص مشترک یاد شده را به همراه دارد.

بنابراین برای کار ساده‌تر با یک فرم AngularJS 2.0، کل فرم را تبدیل به یک ControlGroup کرده و سپس هر کدام از المان‌های ورودی را تبدیل به یک Control مجزا می‌کنیم. کار برقراری این ارتباط، با استفاده از دایرکتیو ویژه‌ای به نام ngControl انجام می‌شود. بنابراین دایرکتیو ngControl، با نامی دلخواه و معین، به تمام المان‌های ورودی، انتساب داده شده‌است.
هرچند در این مثال نام ngControl‌ها با مقدار id هر کنترل یکسان درنظر گرفته شده‌است، اما ارتباطی بین این دو نیست. مقدار id جهت استفاده‌ی در DOM کاربرد دارد و مقدار ngControl توسط AngularJS 2.0 استفاده می‌شود. جهت رسیدن به کدهایی یکدست، بهتر است این نام‌ها را یکسان درنظر گرفت؛ اما هیچ الزامی هم ندارد.

برای بررسی جزئیات این اشیاء کنترل، در المان productName، یک متغیر محلی را به نام productName# تعریف کرده‌ایم و آن‌را به دایرکتیو ngControl انتساب داده‌ایم. این انتساب توسط ngForm انجام شده‌است. زمانیکه AngularJS 2.0 یک متغیر محلی تنظیم شده‌ی به ngForm را مشاهده می‌کند، آن‌را به صورت خودکار به ngControl همان المان ورودی متصل می‌کند. سپس این متغیر محلی را به متد log ارسال کرده‌ایم. این متد در کلاس کامپوننت جاری تعریف شده‌است و کار آن نمایش شیء Control جاری در کنسول developer tools مرورگر است.
<input ngControl="productName" id="productName" required
       #productName="ngForm"
       (change)="log(productName)"
       minlength="3"
       type="text" class="form-control"
       [(ngModel)]="productModel.productName"/>


همانطور که در تصویر مشاهده می‌کنید، عناصر یک شیء Control، در کنسول نمایش داده شده‌اند و در اینجا بهتر می‌توان خواصی مانند valid و امثال آن‌را که به همراه این کنترل وجود دارند، مشاهده کرد. برای مثال خاصیت dirty آن true است چون مقدار آن المان ورودی، تغییر کرده‌است.

بنابراین تا اینجا با استفاده از دایرکتیو ngControl، یک المان ورودی را به یک شیء Control متصل کردیم. همچنین نحوه‌ی تعریف یک متغیر محلی را در المانی و سپس ارسال آن را به کلاس متناظر با کامپوننت فرم، نیز بررسی کردیم.


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

به کنترل‌هایی که به صورت فوق توسط ngControl ایجاد می‌شوند، اصطلاحا implicitly created controls می‌گویند؛ یا به عبارتی ایجاد آن‌ها به صورت «ضمنی» توسط AngularJS 2.0 انجام می‌شود که نمونه‌ای از آن‌را در تصویر فوق نیز مشاهده کردید. این نوع کنترل‌های ضمنی، امکانات اعتبارسنجی محدودی را در اختیار دارند؛ که تنها سه مورد هستند:
الف) required
ب) minlength
ج) maxlength

این‌ها ویژگی‌های استاندارد اعتبارسنجی HTML 5 نیز هستند. نمونه‌ای از اعمال این موارد را با افزودن ویژگی required، به المان‌های فرم ثبت محصولات فوق، مشاهده می‌کنید.
سپس نیاز داریم تا خطاهای اعتبارسنجی را در مقابل هر المان ورودی نمایش دهیم.
<textarea ngControl="description" id="description" required
          #description="ngForm"
          rows="10" type="text" class="form-control"></textarea>
<div class="alert alert-danger" *ngIf="description.touched && !description.valid">
    Description is required.
</div>
پس از افزودن ویژگی required به یک المان، افزودن و نمایش خطاهای اعتبارسنجی، شامل سه مرحله‌ی زیر است:
الف) ایجاد یک div ساده جهت نمایش پیام خطای اعتبار سنجی
ب) افزودن یک متغیر محلی با # و تنظیم شده‌ی به ngForm، جهت دسترسی به شیء کنترل ایجاد شده
ج) استفاده از این متغیر محلی در دایرکتیو ساختاری ngIf* جهت دسترسی به خاصیت valid آن کنترل. بر مبنای مقدار این خاصیت است که تصمیم گرفته می‌شود، پیام اجباری بودن پر کردن فیلد نمایش داده شود یا خیر.
در اینجا یک سری کلاس بوت استرپ 3 هم جهت نمایش بهتر این پیام خطای اعتبارسنجی، اضافه شده‌اند.

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



بهبود شیوه نامه‌ی پیش فرض المان‌های ورودی اطلاعات در AngularJS 2.0

می‌خواهیم اگر اعتبارسنجی یک المان ورودی با شکست مواجه شد، یک حاشیه‌ی قرمز، در اطراف آن نمایش داده شود. این مورد را با توجه به اینکه AngularJS 2.0، شیوه نامه‌های ویژه‌ای را به صورت خودکار به المان‌ها اضافه می‌کند، می‌توان به صورت سراسری به تمام فرم‌ها اضافه کرد. برای این منظور فایل app.component.css واقع در ریشه‌ی پوشه‌ی app را گشوده و تنظیمات ذیل را به آن اضافه کنید:
.ng-touched.ng-invalid{
    border: 1px solid red;
}

ویژگی‌های اضافه شده‌ی در حالت شکست اعتبارسنجی؛ مانند ng-invalid


ویژگی‌های اضافه شده‌ی در حالت موفقیت اعتبارسنجی؛ مانند ng-valid



مدیریت چندین ویژگی اعتبارسنجی یک المان با هم

گاهی از اوقات نیاز است برای یک المان ورودی، چندین نوع اعتبارسنجی مختلف را تعریف کرد. برای مثال فرض کنید که ویژگی‌های required و همچنین minlength، برای نام محصول تنظیم شده‌اند. در این حالت ذکر productName.valid خیلی عمومی است و هر دو حالت اجباری بودن فیلد و حداقل طول آن‌را با هم به همراه دارد:
<div class="alert alert-danger" *ngIf="productName.touched && !productName.valid">
   Name is required.
</div>
بنابراین در این حالت از روش ذیل استفاده می‌شود:
<div *ngIf="productName.touched && productName.errors">
    <div class="alert alert-danger" *ngIf="productName.errors.required">
        Name is required.
    </div>
    <div class="alert alert-danger" *ngIf="productName.errors.minlength">
        Name should be minimum 3 characters.
    </div>
</div>
خاصیت errors نیز یکی دیگر از خواص شیء کنترل است. اگر نال بود، یعنی خطایی وجود ندارد و در غیراینصورت، به ازای هر نوع اعتبارسنجی تعریف شده، خواصی به آن اضافه می‌شوند. بنابراین ذکر productName.errors.required به این معنا است که آیا خاصیت errors، دارای کلیدی به نام required است؟ اگر بله، یعنی این فیلد هنوز پر نشده‌است.
همچنین چون در این حالت productName.touched نیاز است چندین بار تکرار شود، می‌توان آن‌را در یک div محصور کننده‌ی دو div مورد نیاز جهت نمایش خطاهای اعتبارسنجی قرار داد. به علاوه بررسی نال نبودن productName.errors نیز در div محصور کننده صورت گرفته‌است و دیگر نیازی نیست این بررسی را به ngIfهای داخلی اضافه کرد.

نکته 1
اگر علاقمند بودید تا جزئیات خاصیت errors را مشاهده کنید، آن‌را می‌توان توسط pipe توکاری به نام json به صورت موقت نمایش داد و بعد آن‌را حذف کرد:
 <div *ngIf="productName.touched && productName.errors">
  {{ productName.errors | json }}

نکته 2
بجای ذکر مستقیم عدد سه در «minimum 3 characters»، می‌توان این عدد را مستقیما از تعریف ویژگی minlength نیز استخراج کرد:
 Name should be minimum {{ productName.errors.minlength.requiredLength }} characters.


بررسی ngForm

شبیه به ngControl که یک المان ورودی را به یک کنترل AngularJS 2.0 متصل می‌کند، دایرکتیو دیگری نیز به نام ngForm وجود دارد که کل فرم را به شیء ControlGroup بایند می‌کند و برخلاف ngControl، نیازی به ذکر صریح آن وجود ندارد. هر زمانیکه AngularJS 2.0، المان استاندارد فرمی را در صفحه مشاهده می‌کند، این اتصالات را به صورت خودکار برقرار خواهد کرد.
ngForm دارای خاصیتی است به نام ngSumbit که از نوع EventEmitter است (نمونه‌ای از آن را در مبحث کامپوننت‌های تو در تو پیشتر ملاحظه کرده‌اید). بنابراین از آن می‌توان جهت اتصال رخداد submit فرم، به متدی در کلاس کامپوننت خود، استفاده کرد. متد متصل به این رخداد، زمانی فراخوانی می‌شود که کاربر بر روی دکمه‌ی submit کلیک کند:
 <form #f="ngForm" (ngSubmit)="onSubmit(f.form)">
همچنین در اینجا متغیر محلی f جهت دسترسی به شیء ControlGroup و ارسال آن به متد onSubmit تعریف شده‌است (شبیه به متغیرهای محلی دسترسی به ngControl که پیشتر جهت نمایش خطاهای اعتبارسنجی، اضافه کردیم).

پس از تعریف این رخداد و اتصال آن در قالب کامپوننت، اکنون می‌توان متد onSubmit را در کلاس آن نیز اضافه کرد.
onSubmit(form): void {
   console.log(form);
}
فعلا هدف از این متد، نمایش جزئیات شیء form دریافتی، در کنسول developer tools است.



غیرفعال کردن دکمه‌ی submit در صورت وجود خطاهای اعتبارسنجی

در قسمت بررسی ngForm، یک متغیر محلی را به نام f ایجاد کردیم که به شیء ControlGroup فرم جاری اشاره می‌کند. از این متغیر و خاصیت valid آن می‌توان با کمک property binding به خاصیت disabled یک دکمه، آن‌را به صورت خودکار فعال یا غیرفعال کرد:
<button [disabled]="!f.valid"
        type="submit" class="btn btn-primary">
    Submit
</button>
هر زمانیکه کل فرم از لحاظ اعتبارسنجی مشکلی نداشته باشد، دکمه‌ی submit فعال می‌شود و برعکس.



نمایش فرم افزودن محصولات توسط سیستم Routing

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



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

برای اتصال المان‌های فرم به یک مدل، این مدل را به صورت یک خاصیت عمومی، در سطح کلاس کامپوننت فرم، تعریف می‌کنیم:
 productModel = <IProduct>{}; // creates an empty object of an interface
اگر از اینترفیسی مانند IProduct که در قسمت‌های قبل این سری تعریف شد، نیاز است شیء جدیدی ساخته شود، الزاما نیازی نیست تا یک کلاس جدید را از آن مشتق کرد و بعد متغیر new ClassName را تهیه کرد. در TypeScript می‌توان به صورت خلاصه از syntax فوق نیز استفاده کرد.
پس از تعریف خاصیت productModel، اکنون کافی است با استفاده از two-way data binding، آن‌را به المان‌های فرم نسبت دهیم. برای مثال:
<textarea ngControl="description" id="description" required
          #description="ngForm"
          rows="10" type="text" class="form-control"
          [(ngModel)]="productModel.description"></textarea>
در اینجا با استفاده از ngModel و انقیاد دو طرفه، کار اتصال به خاصیت توضیحات شیء محصول انجام شده‌است. اکنون بلافاصله تغییرات اعمالی به فرم، به مدل متناظر منعکس می‌شود و برعکس. این ngModel را به تمام المان‌های ورودی فرم متصل خواهیم کرد.
پس از تعریف یک چنین اتصالی، دیگر نیازی به مقدار دهی پارامتر onSubmit(f.form) نیست. زیرا شیء productModel، در متد onSumbit در دسترس است و این شیء همواره حاوی آخرین تغییرات اعمالی به المان‌های فرم است.

پس از اینکه فرم را به مدل آن متصل کردیم، فایل product.service.ts را گشوده و متد جدید addProduct را به آن اضافه کنید:
addProduct(product: IProduct): Observable<IProduct> {
    let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
    return this._http.post(this._addProductUrl, JSON.stringify(product), options)
        .map((response: Response) => <IProduct>response.json())
        .do(data => console.log("Product: " + JSON.stringify(data)))
        .catch(this.handleError);
}
کار این متد، ارسال شیء محصول به یک اکشن متد برنامه‌ی ASP.NET MVC جاری است. با جزئیات کار با obsevables درمطلب «دریافت اطلاعات از سرور» پیشتر آشنا شده‌ایم.
نکته‌ی مهم اینجا است که content type پیش فرض ارسالی متد post آن، plain text است و در این حالت ASP.NET MVC شیء JSON دریافتی از کلاینت را پردازش نخواهد کرد. بنابراین نیاز است تا هدر content type را به صورت صریحی در اینجا ذکر نمود؛ در غیراینصورت در سمت سرور، شاهد نال بودن مقادیر دریافتی از کاربران خواهیم بود.
امضای سمت سرور متد دریافت اطلاعات از کاربر، چنین شکلی را دارد (تعریف شده در فایل Controllers\HomeController.cs):
 [HttpPost]
public ActionResult AddProduct(Product product)
{

اشیاء هدرها و تنظیمات درخواست، در متد addProduct سرویس ProductService، در ماژول‌های ذیل تعریف شده‌اند که باید به ابتدای فایل product.service.ts اضافه شوند:
 import { Headers, RequestOptions } from 'angular2/http';

پس از تعریف متد addProduct در سرویس ProductService، اکنون با استفاده از ترزیق این سرویس به سازنده‌ی کلاس فرم ثبت یک محصول جدید، می‌توان متد this._productService.addProduct را جهت ارسال productModel به سمت سرور، در متد onSubmit فراخوانی کرد:
//Same as before…
import { IProduct } from './product';
import { ProductService } from './product.service';
 
@Component({
//Same as before…
})
export class ProductFormComponent {
 
    productModel = <IProduct>{}; // creates an empty object of an interface
 
    constructor(private _productService: ProductService, private _router: Router) { }
 
    //Same as before… 

    onSubmit(form): void {
        console.log(form);
        console.log(this.productModel);
 
        this._productService.addProduct(this.productModel)
            .subscribe((product: IProduct) => {
                console.log(`ID: ${product.productId}`);
                this._router.navigate(['Products']);
            });
    }
}
همانطور که ذکر شد، از آنجائیکه شیء productModel حاوی آخرین تغییرات اعمالی توسط کاربر است، اکنون می‌توان پارامتر form متد onSubmit را حذف کرد.
در اینجا پس از فراخوانی متد addProduct، متد subscribe، در انتهای زنجیره، فراخوانی شده‌است. کار آن هدایت کاربر به صفحه‌ی نمایش لیست محصولات است. در اینجا this._router از طریق تزریق وابستگی‌های سرویس مسیریاب به سازنده‌ی کلاس، تامین شده‌است. نمونه‌ی آن‌را در قسمت «افزودن دکمه‌ی back با کدنویسی» مربوطه به مطلب آشنایی با مسیریابی، پیشتر مطالعه کرده‌اید.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part10.zip


خلاصه‌ی بحث

فرم‌های template driven در AngularJS 2.0 به این نحو طراحی می‌شوند:
 1) ابتدا فرم HTML را به حالت معمولی آن طراحی می‌کنیم؛ با تمام المان‌های آن.
 2) به تمام المان‌های فرم، دیراکتیو ngControl را متصل می‌کنیم، تا AngularJS 2.0 آن‌را تبدیل به یک کنترل خاص خودش کند. کنترلی که دارای خواصی مانند valid و touched است.
 3) سپس برای دسترسی به این کنترل ایجاد شده‌ی به صورت ضمنی، یک متغیر محلی آغاز شده‌ی با # را به تمام المان‌ها اضافه می‌کنیم.
 4) اعتبارسنجی‌هایی را مانند required  به المان‌های فرم اضافه می‌کنیم.
 5) از متغیر محلی تعریف شده و ngIf* برای بررسی خواصی مانند valid و touched برای نمایش خطاهای اعتبارسنجی کمک گرفته می‌شود.
 6) پس از تعریف فرم، تعریف ngControlها، تعریف متغیر محلی شروع شده‌ی با # و افزودن خطاهای اعتبارسنجی، اکنون نوبت به ارسال این اطلاعات به سرور است. بنابراین رخداد ngSubmit را باید به متدی در کلاس کامپوننت جاری متصل کرد.
 7) اکنون که با کلیک بر روی دکمه‌ی submit فرم، متد onSubmit متصل به ngSubmit فراخوانی می‌شود، نیاز است بین المان‌های فرم HTML و کلاس کامپوننت، ارتباط برقرار کرد. این‌کار را توسط two-way data binding و تعریف ngModel بر روی تمام المان‌های فرم، انجام می‌دهیم. این ngModelها، به یک خاصیت عمومی که متناظر است با وهله‌ای از شیء مدل فرم، متصل هستند. بنابراین این مدل، در هر لحظه، بیانگر آخرین تغییرات کاربر است و از آن می‌توان برای ارسال اطلاعات به سرور استفاده کرد.
 8) پس از اتصال فرم به کلاس متناظر با آن، اکنون سرویس محصولات را تکمیل کرده و به آن متد HTTP Post را جهت ارسال اطلاعات سمت کاربر، به سرور، اضافه می‌کنیم. در اینجا نکته‌ی مهم، تنظیم content type ارسالی به سمت سرور است. در غیراینصورت فریم ورک سمت سرور قادر به تشخیص JSON بودن این اطلاعات نخواهد شد.
بازخوردهای دوره
حذف یک ردیف از اطلاعات به همراه پویانمایی محو شدن اطلاعات آن توسط jQuery در ASP.NET MVC
سلام. ابتدا از مطلب خوبتون تشکر میکنم . واقعا بسیار جالب و کاربردیه .
من از این مثال استفاده کردم و درست کار میکنه ولی وقتی میخوام  از کاربر تایید برای حذف بگیرم ( با استفاده از  jquery-impromptu ) یه مشکلی پیش میاد .
حالت عادی که Id مربوط به پیغاممون درست انتخاب میشه.


ولی وقتی تایید میگیرم

موس روی messageid هستش و نوشته‌ی قرمز مقدار id هستش که قطعا درست نیست.

مسلما مشکل از این خطه که id رو انتخاب میکنه

var span = $(this);
var messageid = span.attr('id').replace('row-', '');

ولی نمیدونم چجوری id رو انتخاب کنم!

اینم کدها که تایید کاربرو بهش اضافه کردم

<script type="text/javascript">
        $(function() {
            $('span[id^="row"]').click(function() {
                $.prompt("آیا برای حذف اطلاعات موجود اطمینان دارید ؟",
                    {
                        title: 'Title',
                        buttons: { "بله": true, "خیر": false },
                        focus: 2,
                        submit: function(e, v, m, f) {
                            if (!v) {
                                return;// وقتی کاربر تایید نکرد
                            } else {// وقتی کاربر تایید کرد
                                var span = $(this); 
                                var messageid = span.attr('id').replace('row-', '');
                                var tableRow = span.parent().parent();
                                $.ajax({
                                    type: "POST",
                                    url: '@postUrl',
                                    data: JSON.stringify({ id: messageid }),
                                    contentType: "application/json; charset=utf-8",
                                    dataType: "json",
                                    complete: function(xhr, status) {
                                        var data = xhr.responseText;
                                        if (xhr.status == 403) {
                                            window.location = "/login";
                                        } else if (status === 'error' || !data || data == "nok") {
                                            var noty = window.noty({ text: "خطایی رخ داده", type: 'error', layout: 'center', timeout: 2500 });
                                        } else {
                                            $(tableRow).fadeTo(600, 0, function() {
                                                $(tableRow).remove();
                                            });
                                            var noty = window.noty({ text: "پیغام حذف شد", type: 'information', layout: 'center', timeout: 2500 });
                                        }
                                    }                            
                                });

                            }

                        }                
                    });
            
        });
        });
    </script>