مطالب
شروع به کار با 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 بودن این اطلاعات نخواهد شد.
مطالب
ایجاد تایمرها در برنامه‌های Angular
عموما در برنامه‌های جاوا اسکریپتی با استفاده از متدهای setTimeout و setInterval می‌توان یک تایمر را ایجاد کرد. اما در برنامه‌های Angular با توجه به استفاده‌ی از کتابخانه‌ی RxJS، امکان ایجاد تایمرهای reactive نیز وجود دارد که در این مطلب آن‌ها را مرور خواهیم کرد.


ایجاد تایمرهای متوالی و بی‌وقفه

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


در این مثال می‌خواهیم تایمری را ایجاد کنیم که هر ثانیه یکبار، کدی را اجرا کند:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/interval";
import { Subscription } from "rxjs/Subscription";

@Component()
export class UsingTimersComponent {

  private intervalSubscription: Subscription;
  interval = 0;

  startInterval() {
    const interval = Observable.interval(1000);
    this.intervalSubscription = interval.subscribe(i => this.interval += i);
  }

  stopInterval() {
    this.intervalSubscription.unsubscribe();
  }
}
با این قالب:
<div class="panel panel-default">
  <div class="panel-heading">
    <h2 class="panel-title">Observable.interval(1000)</h2>
  </div>
  <div class="panel-body">
    <div>
      <label>interval: </label> {{interval}}
    </div>
    <div>
      <button (click)="startInterval()" class="btn btn-success">Start</button>
      <button (click)="stopInterval()" class="btn btn-danger">Stop</button>
    </div>
  </div>
</div>
عملگر interval باید از مسیر rxjs/add/observable/interval دریافت شود که در ابتدای تعاریف کامپوننت مشخص شده‌است.
 پس از آن فراخوانی Observable.interval(1000) یک Observable را ایجاد می‌کند که توانایی صدور رخ‌دادهایی را در بازه‌های زمانی متوالی 1000 میلی ثانیه‌ای، دارا است.
اکنون مشترکین به آن، اعداد متوالی شروع شده‌ی از صفر را در هر ثانیه یکبار، دریافت می‌کنند:
this.intervalSubscription = interval.subscribe(i => this.interval += i);
این تایمر، به نحوی که تعریف شده‌است، تا ابد ادامه پیدا خواهد کرد. برای توقف آن نیاز است همانند روال معمول کار با Observableها، اشتراک به آن را لغو کرد:
this.intervalSubscription.unsubscribe();


مطلع شدن از پایان کار یک تایمر

با استفاده از اپراتور finally که از مسیر rxjs/add/operator/finally قابل import است، می‌توان رخ‌داد لغو اشتراک به این Observable و یا همان خاتمه‌ی تایمر را در اینجا دریافت کرد:
this.intervalSubscription = interval
      .finally(() => console.log("All done!"))
      .subscribe(i => this.interval += i);


ایجاد تایمرهای خود متوقف شونده

با استفاده از عملگر Observable.timer که در مسیر rxjs/add/observable/timer قرار دارد، می‌توان تایمری را ایجاد کرد که پس از یک تاخیر مشخص شده‌، اجرا شود و بلافاصله خاتمه یابد:
const timer = Observable.timer(1000);
timer.subscribe(data => console.log('ding!'));
در اینجا تایمری ایجاد شده‌است که پس از یک ثانیه اجرا شده و کد نمایش ding را در کنسول مرورگر اجرا می‌کند. سپس به صورت خودکار خاتمه خواهد یافت. در اینجا data نیز مساوی صفر است (اولین بار اجرای تایمر).
این تایمر امکان اجرای در بازه‌های زمانی مشخصی را نیز دارا است:
const moreThanOne$ = Observable.timer(2000, 500);
moreThanOne$.subscribe(data => console.log('timer with args', data));
اولین پارامتر آن مشخص می‌کند که این تایمر باید پس از 2 ثانیه تاخیر، شروع به کار کند و دومین آرگومان آن مشخص می‌کند که این تایمر تا ابد، با فواصل زمانی هر 500 میلی‌ثانیه یکبار، اجرا خواهد شد.


محدود کردن تعداد بار اجرای تایمر

اگر Observable.timer با پارامتر دوم آن بکار رود، بی‌نهایت بار اجرا خواهد شد. اما می‌توان این تعداد بار اجرا را توسط اپراتور take که از مسیر rxjs/add/operator/take قابل import است، محدود کرد:
let moreThanOne$ = Observable.timer(2000, 500).take(3);
moreThanOne$.subscribe(data => console.log('timer with args', data));
در اینجا تایمر تعریف شده، پس از یک وقفه‌ی آغازین 2 ثانیه‌ای شروع به کار می‌کند. سپس تنها دو بار دیگر در بازه‌های متوالی زمانی 500 میلی ثانیه یکبار، اجرا خواهد شد. یعنی جمعا سه بار با توجه به take(3) اجرا خواهد شد.


اجرای با تاخیر بازه‌های زمانی

با استفاده از اپراتور delay که از مسیر rxjs/add/operator/delay قابل import است، می‌توان هر بار اجرای callback تایمر را با یک تاخیر دریافت کرد:
const start = new Date();
const stream$ = Observable.interval(500).take(3);
stream$.delay(300).subscribe(x => {
    console.log('val',x);
    console.log( new Date() - start );
})
در اینجا تایمر از نوع interval تعریف شده، با توجه به استفاده‌ی از عملگر take، تنها سه بار اجرا می‌شود. اما این اجراها با تاخیری 300 میلی‌ثانیه‌ای به مشترکین آن‌ها اطلاع رسانی می‌گردند. به این ترتیب خروجی لاگ شده‌ی این عملیات به صورت ذیل خواهد بود:
val:0
800ms
val:1
1300ms
val:2
1800ms


ایجاد یک تایمر شمارش معکوس

فرض کنید می‌خواهید تایمری را ایجاد کنید که در طی یک شمارش معکوس، از عدد 10000 شروع شود و هر ثانیه یکبار 1000 واحد از آن کاهش یابد و زمانیکه به صفر رسید، متوقف شود.
این تایمر پس از import وابستگی‌های آن:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/timer";
import "rxjs/add/operator/finally";
import "rxjs/add/operator/takeUntil";
import "rxjs/add/operator/map";
یک چنین تعریفی را پیدا می‌کند:
const interval = 1000;
const duration = 10 * 1000;
const stream$ = Observable.timer(0, interval)
      .finally(() => console.log("All done!"))
      .takeUntil(Observable.timer(duration + interval))
      .map(value => duration - value * interval);
stream$.subscribe(value => console.log(value));
در اینجا تایمر تعریف شده با توجه به آرگومان صفر تاخیر آن، بلافاصله شروع به کار می‌کند. همچنین با توجه به عدد interval آن، هر یک ثانیه یکبار اعداد صفر، یک و ... را به مشترکین خود ارسال خواهد کرد. اکنون می‌خواهیم این تایمر دقیقا پس از 11 ثانیه متوقف شود. یکی از روش‌های پیاده سازی آن استفاده از takeUntil است که در اینجا یک تایمر خود متوقف شوند را دریافت کرده‌است. این تایمر دقیقا پس از 11 ثانیه از شروع عملیات، یکبار اجرا شده و بلافاصله خاتمه پیدا می‌کند. همین صدور رخ‌داد، کار takeUntil را به پایان می‌رساند که این مورد نیز سبب خاتمه‌ی تایمر اصلی می‌شود.
در اینجا چون اعداد صادر شده‌ی از طرف تایمر، افزایشی هستند، نیاز است به روشی آن‌ها را تغییر داد. در یک چنین حالتی از اپراتور map استفاده می‌شود. در اینجا value، هربار مقدار افزایشی شروع شده‌ی از صفر را ارائه می‌دهد. توسط عملگر map، این خروجی افزایشی را به یک خروجی کاهشی تبدیل کرده‌ایم تا بتوان به یک تایمر شمارش معکوس رسید.


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

Observable.timer با هر بار اجرا، اعداد شروع شده‌ی از صفر را به مشترکین ارسال می‌کند. اگر در این بین از اپراتور timeInterval قرار گرفته‌ی در مسیر rxjs/add/operator/timeInterval استفاده شود، این مقدار ارسالی از نوع مخصوص <TimeInterval<number خواهد بود که دارای خواص value و interval است:
const source = Observable.timer(0, 1000)
      .timeInterval()
      .map(x => x.value + ":" + x.interval)
      .take(5);

const subscription = source.subscribe(
      x => console.log("Next timeInterval: " + x),
      err => console.log("Error: " + err),
      () => console.log("Completed")
    );
در اینجا value همان صفر، یک و ... است و interval بیانگر زمان سپری شده‌ی بین دو صدور رخ‌داد می‌باشد.
در این مثال با استفاده از متد map، یک خروجی سفارشی تهیه شده‌است. اگر صرفا علاقمند به دریافت مقدار خاصیت interval باشید، می‌توان به صورت ذیل نیز عمل کرد:
const source = Observable.timer(0, 1000)
      .timeInterval()
      .pluck("interval")
      .take(5);
عملگر pluck که در مسیر rxjs/add/operator/pluck قرار دارد، خاصیت و یا خاصیت‌هایی از منبع را جهت بازگشت، انتخاب می‌کند. برای مثال در اینجا خاصیت interval یک شیء TimeInterval انتخاب شده‌است.


تعلیق و از سرگیری مجدد تایمرها

با قطع اشتراک از یک منبع تایمر، سبب توقف کامل آن خواهیم شد. اما اگر برای مدتی بخواهیم آن‌را در حالت تعلیق قرار دهیم، می‌توان به صورت ذیل عمل کرد:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/never";
import "rxjs/add/observable/timer";
import { Subject } from "rxjs/Subject";

  tick: number;
  pauser = new Subject();
  tickerSource = new Subject();
  startTicker() {
    Observable.timer(0, 1000)
      .subscribe(this.tickerSource);

    this.pauser
      .switchMap(paused => paused ? Observable.never() : this.tickerSource).
      subscribe(t => this.tickerFunc(t));

    this.pauser.next(false); // resume
  }

  tickerFunc(tick) {
    this.tick = tick;
  }

  pauseTicker() {
    this.pauser.next(true);
  }

  resumeTicker() {
    this.pauser.next(false);
  }
نکته‌ی اصلی این طراحی در switchMap و Observable.never آن نهفته‌است. در اینجا وجود Subject سبب صدور رخدادی به مشترکین آن می‌شود. اگر توسط متد next آن false ارسال شود، سبب از سرگیری مجدد منبع اصلی یا همان تایمر برنامه می‌شود و اگر true ارسال شود، عملیات فراخوانی tickerFunc را با فراخوانی Observable.never به حالت تعلیق می‌برد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات اشتراک‌ها
تقویم شمسی رسپانسیو برای بوت استراپ
امروز در حین مطالعه راهنما متوجه شدم خروجی unixtime خروجی پیش فرضش هست و خروجی‌های زیر رو هم شاملش میشه:
The dateFormat to be used for the altField option. acceptable value: unix,gregorian,g,u,YYYY/mMM/DD  
برای typescript هم شامل typing هست.
نظرات مطالب
بهبود امنیت CSP با استفاده از معرفی هش‌های اسکریپت‌های Inline
یک نکته‌ی تکمیلی: استفاده از nonce بجای هش

روش دیگری هم برای مشخص کردن اسکریپت‌ها و شیوه‌نامه‌های inline وجود دارد که nonce نامیده می‌شود. nonce، یک مقدار منحصربفرد است که باید به ازای هر درخواست، یکبار دیگر به صورت خودکار، مجددا تولید شود (اگر ثابت باشد، مهاجم می‌تواند مقدار مشخص آن‌را به اسکریپت‌های خودش اضافه کند):
script-src 'nonce-rAnd0m'
یعنی قبل از هرکاری باید میان‌افزاری که کار تولید CSP Headers فوق را انجام می‌دهد، این مقدار را تولید کند. سپس می‌توان این مقدار را به ویژگی nonce برای مثال تگ اسکریپت، اضافه کرد:
<script nonce="rAnd0m">
</script>
مزیت آن، عدم نیاز به محاسبه و یا درج هش این قطعه اسکریپت، مطابق نکات مطلب جاری است؛ اما باید به صورت پویا به ازای هر درخواستی، مجددا در سمت سرور، تولید شود. همچنین تولید مجدد این مقدار، با کش شدن اطلاعات صفحه در تضاد است و باید به آن دقت داشت.

اگر از کتابخانه‌ی NetEscapades.AspNetCore.SecurityHeaders استفاده می‌کنید، فراخوانی متد WithNonce آن:
builder.AddScriptSrc().Self().WithNonce()
به صورت خودکار هدر مرتبط را تولید می‌کند. همچنین مقدار تولیدی آن‌را نیز در HttpContext.Items، درج می‌کند:
HttpContext.Items["NETESCAPADES_NONCE"]
به این روش، برای مثال یک Tag Helper و یا قسمت دیگری از برنامه که کار مقدار دهی nonce را به عهده دارد، می‌تواند به مقدار خودکار تولید شده‌ی توسط میان‌افزار، دسترسی داشته باشد (ابتدا میان‌افزار CSP اجرا می‌شود و سپس کار رندر صفحه به صورت جداگانه‌ای انجام می‌شود).

یک نکته: هنگام درج مقدار nonce در attributes، به HTML encoding آن دقت داشته باشید؛ این مقدار نباید encode شود (یا تغییر کند).
نظرات مطالب
ایجاد سیستم وضعیت آب و هوا مانند گوگل (بخش اول)
یه راه دیگه هم استفاده از Yahoo Query Language هست،  می تونید با کوئری زیر اطلاعات آب و هوای شهر مورد نظر رو در قالب JSON دریافت کنید
select * from weather.forecast where woeid in (select woeid from geo.placefinder where text="CityName")
نظرات مطالب
راه‌های کم کردن احتمال اسپم شدن ایمیل‌های ارسالی توسط SMTP Client
سلام
لطفا کدهای فوق رو به صورت زیر اصلاح کنید. هنگام استفاده مشکل دارند.
AlternateView plainView = AlternateView.CreateAlternateViewFromString(
                        Regex.Replace(EmailBody, @"<(.|\n)*?>", string.Empty), null, "text/plain");
AlternateView htmlView = AlternateView.CreateAlternateViewFromString(EmailBody, null, "text/html");
MyMsg.AlternateViews.Add(plainView);
MyMsg.AlternateViews.Add(htmlView);

البته من namespace هارو در بالای کلاس تعریف کردم و از اینجا حذف کردم.
using System.Net.Mail;
using System.Text.RegularExpressions;

با تشکر
نظرات مطالب
ASP.NET MVC #9
با سلام
نحوه استفاده از ActionLink  برای چند پارامتر چگونه است . در صورتی که در Array  آیتم بعدی را اضافه کنیم به صورت ؟ و یک queryString  تبدیل می‌شود . در صورتی که بخواهیم به صورت یک / پارامتر‌های بعدی را مقدار دهی کنیم به چه صورت است ؟

@Html.ActionLink(item.Name, "Details", new { id = item.ProductNumber , Name=item.Name })
http://localhost/Products/Details/D123?Name=Super%20Fast%20Bike
به این صورت تبدیل شود
http://localhost/Products/Details/D123/Super%20Fast%20Bike


با تشکر

 
نظرات مطالب
خواندنی‌های 27 شهریور
سلام،
به تولبار شما باید یک سری آیتم اضافه شده باشد مطابق تصاویر ذیل:
http://blogs.msdn.com/vsdata/archive/2009/07/24/visual-basic-power-packs-is-included-in-visual-studio-2008-sp1.aspx
و یک نمونه از موارد کاربرد آن
http://msdn.microsoft.com/en-us/vbasic/bb735936.aspx
نظرات مطالب
خواندن سریع اطلاعات فایل اکسل و ذخیره در بانک SQL
با سلام و تشکر. این برنامه پیغام میدهد List وجود ندارد.
 SELECT * FROM [List$]
لطفا راهنمایی بفرمایید من چطور میتوانم نام شیت‌ها را بدست بیاورم؟
مطالب
نمایش اخطارها و پیام‌های بوت استرپ به کمک TempData در ASP.NET MVC
در بوت استرپ برای نمایش اعلانی به کاربر، از کلاس alert می‌توان استفاده کرد. برای نمایش این اعلان کافی است محتوای خود را درون یک div با کلاس alert قرار دهیم: 
<div class="alert">
    نمایش اعلانات
</div>

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

همچنین اگر مایل بودید می‌توانید با افزودن یک دکمه با کلاس close و ویژگی data-dismiss مساوی alert، امکان بستن پیام را در اختیار کاربر قرار دهید: 

<div class="alert alert-warning alert-dismissable">
  <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
  نمایش اعلان
</div>

در ادامه قصد داریم این پیام را بعد از ثبت اطلاعات، به کاربر نمایش دهیم. یعنی در داخل کد، امکان صدا زدن این نوع پیام‌ها را داشته باشیم.

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

کلاس Alert

public class Alert
{
        public const string TempDataKey = "TempDataAlerts";
        public string AlertStyle { get; set; }
        public string Message { get; set; }
        public bool Dismissable { get; set; }
}

در کلاس فوق خصوصیت یک alert را تعریف کرده‌ایم (از خاصیت TempDataKey جهت پاس دادن alert‌ها به view استفاده می‌کنیم).

  کلاس AlertStyles
public class AlertStyles
{
        public const string Success = "success";
        public const string Information = "info";
        public const string Warning = "warning";
        public const string Danger = "danger";
}
کلاس فوق نیز جهت نگهداری اسامی کلاس‌های alert، مورد استفاده قرار می‌گیرد. قدم بعدی، استفاده از کلاس‌های فوق و انتقال alerts توسط TempData به داخل viewها می‌باشد. برای جلوگیری از زیاد شدن حجم کدهای تکراری داخل کنترلرها و همچنین به عنوان یک Best practice، یک کنترلر Base را برای اینکار تعریف می‌کنیم و متدهای موردنیاز را داخل آن می‌نویسیم:
public class BaseController : Controller
{
        public void Success(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Success, message, dismissable);
        }

        public void Information(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Information, message, dismissable);
        }

        public void Warning(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Warning, message, dismissable);
        }

        public void Danger(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Danger, message, dismissable);
        }

        private void AddAlert(string alertStyle, string message, bool dismissable)
        {
            var alerts = TempData.ContainsKey(Alert.TempDataKey)
                ? (List<Alert>)TempData[Alert.TempDataKey]
                : new List<Alert>();

            alerts.Add(new Alert
            {
                AlertStyle = alertStyle,
                Message = message,
                Dismissable = dismissable
            });

            TempData[Alert.TempDataKey] = alerts;
        }
}
از متدهایی که به صورت عمومی تعریف شده‌اند جهت ارسال پیام به view استفاده می‌کنیم. متد AddAlert نیز جهت ایجاد لیستی از پیام‌ها(Alert) مورداستفاده قرار می‌گیرد؛ زیرا ممکن است بخواهید هم زمان از چندین متد عمومی فوق استفاده کنید، یعنی چندین پیام را به کاربر نمایش دهید. 

نکته: در کد فوق از TempData جهت پاس دادن شیء alerts استفاده کرده‌ایم. TempData به صورت short-lived عمل می‌کند به دو دلیل: 1- بلافاصله بعد از خوانده شدن، حذف خواهد شد. 2- پس از پایان درخواست از بین خواهد رفت. از TempData جهت پاس دادن داده‌ها از درخواست فعلی به درخواست بعدی (redirect از یک صفحه به صفحه دیگر) استفاده می‌شود. یعنی در زمان redirect سعی می‌کند داده‌های بین redirectها را در خود نگه دارد. اگر از ViewBag و ViewData استفاده می‌کردیم داده‌های داخل آنها بلافاصله بعد از redirect شدن null می‌شدند.
به طور مثال اکشن متد زیر را در نظر بگیرید:
public ActionResult Index()
        {
            var userInfo = new
            {
                Name = "Sirwan",
                LastName = "Afifi",
            };
 
            ViewData["User"] = userInfo;
            ViewBag.User = userInfo;
            TempData["User"] = userInfo;

            return RedirectToAction("About");
        }
view :
@{
    ViewBag.Title = "About";
}

<h1>Tempdata</h1><p>@TempData["User"]</p>
<h1>ViewData</h1><p>@ViewData["User"]</p>
<h1>ViewBag</h1><p>@ViewBag.User</p>
اگر کد فوق را تست کنید خواهید دید که در خروجی تنها اطلاعات داخل TempData نمایش داده می‌شود.
معمولاً برای ارسال داده‌های خطاها از TempData استفاده می‌شود.

اکنون در هر کنترلری که می‌خواهید پیامی را به صورت alert، پس از ثبت اطلاعات به کاربر نمایش دهید، باید از کنترلر BaseController  ارث‌بری کنید:
public class NewsController : BaseController
{
readonly INewsService _newsService;
        readonly IUnitOfWork _uow;
        public NewsController(INewsService newsService, IUnitOfWork uow)
        {
            _newsService = newsService;
            _uow = uow;
        }
        [HttpPost]
        [ValidateAntiForgeryToken]
        [ValidateInput(false)]
        public ActionResult Create(News news)
        {
            if (ModelState.IsValid)
            {
                _newsService.AddNews(news);
                _uow.SaveChanges();
                Success(string.Format("خبر با عنوان  <b>{0}</b> با موفقیت ذخیره گردید!", news.Title), true);
                return RedirectToAction("Index");
            }
            Danger("خطا در هنگام ثبت اطلاعات ");
            return View(news);
        }
        [HttpPost]
        public ActionResult Delete(int id)
        {
            _newsService.DeleteNewsById(id);
            _uow.SaveChanges();
            Danger("اطلاعات مورد نظر با موفقیت حذف گردید!", true);
            return RedirectToAction("Index");
        }
}

نمایش پیام‌ها 
برای نمایش پیام‌ها یک partial view با نام _Alerts در مسیر Views\Shared ایجاد می‌کنیم: 
@{
    var alerts = TempData.ContainsKey(Alert.TempDataKey)
                ? (List<Alert>)TempData[Alert.TempDataKey]
                : new List<Alert>();

    if (alerts.Any())
    {
        <hr />
    }

    foreach (var alert in alerts)
    {
        var dismissableClass = alert.Dismissable ? "alert-dismissable" : null;
        <div class="alert alert-@alert.AlertStyle @dismissableClass">
            @if (alert.Dismissable)
            {
                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
            }
            @Html.Raw(alert.Message)
        </div>
    }
}
در کد فوق، ابتدا شیء alerts را از TempData دریافت کرده‌ایم و توسط یک حلقه foreach، داخل آن به ازای هر آیتم، آن را پیمایش می‌کنیم و در نهایت کد html متناظر با هر alert را در خروجی نمایش می‌دهیم.
اکنون جهت استفاده از partial view فوق در جایی که می‌خواهید پیام نمایش داده شود partial view فوق را فراخوانی کنید (به عنوان مثال داخل فایل Layout): 
<div>
                    @{ Html.RenderPartial("_Alerts"); }
                    @RenderBody()
</div>