نظرات مطالب
فعال سازی قسمت ارسال فایل و تصویر ویرایشگر آنلاین RedActor در ASP.NET MVC
این کد من که نوشتمش و جواب گرفتم.
[HttpPost]
public ActionResult ImageUpload(HttpPostedFileBase file)
{
   string path = "";
   var FileName = "";
   if (file.ContentLength > 0)
   {
     FileName = Path.GetFileName(file.FileName);
     string Extension = file.ContentType;
     string[] ExtensionList = { "image/jpg", "image/jpeg", "image/gif", "image/pmg" };
     if (ExtensionList.Contains(Extension.ToLower()))
     {
         path = Path.Combine(Server.MapPath("~/Images/uploads"), FileName);
         file.SaveAs(path);
     }
   }

   var array = new { filelink = @"Images\uploads\" + FileName };
   return Json(array, System.Net.Mime.MediaTypeNames.Text.Plain, JsonRequestBehavior.AllowGet);
}
امید وارم به کارت بیاد.  
بازخوردهای دوره
استفاده از StructureMap به عنوان یک IoC Container
در انجمن آن بیشتر بحث شده؛ در اینجا
در نگارش بعدی، ObjectFactory استاتیک حذف می‌شود. بجای آن باید بنویسید:
var container = new Container(x => {
// تنظیمات در اینجا
});
و بعد مثلا:
var controller = container.GetInstance(controllerType) as SomeType;
در جاهائیکه مستقیما با ObjectFactory کار می‌کردید، بهتر است IContainer آن‌را مورد استفاده قرار دهید:
public class MyController
{
    public MyController(IContainer container)
    {
    }
}
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت نهم - مسیریابی
یک برنامه، از صفحات و Viewهای مختلفی تشکیل می‌شود و Routing یا مسیریابی، امکان ناوبری بین این Viewها را میسر می‌کند. یک برنامه‌ی AngularJS 2.0، یک برنامه‌ی تک صفحه‌ای وب است. به این معنا که تمام Viewهای برنامه، در یک صفحه نمایش داده می‌شوند؛ که معمولا همان index.html سایت است. هدف از سیستم مسیریابی، مدیریت نحوه‌ی نمایش و قرارگیری این Viewها، درون تک صفحه‌ی برنامه است.


برپایی تنظیمات اولیه‌ی سیستم مسیریابی در AngularJS 2.0

برای کار با سیستم مسیریابی AngularJS 2.0، ابتدا باید اسکریپت‌های آن به صفحه اضافه شوند. در ادامه المان پایه‌ای تعریف شده و سپس باید سرویس پروایدر مسیریابی را رجیستر کرد. جزئیات این موارد را در ادامه بررسی می‌کنیم:

الف) سرویس مسیریابی، جزئی از angular2/core نیست. به همین جهت مدخل اسکریپت متناظر با آن باید به صفحه‌ی اصلی سایت اضافه شود که این مورد، در قسمت اول بررسی پیشنیازهای نصب AngularJS 2.0 صورت گرفته‌است:
 <!-- Required for routing -->
<script src="~/node_modules/angular2/bundles/router.dev.js"></script>
این تعریف در فایل Views\Shared\_Layout.cshtml (و یا index.html) پروژه‌ی جاری موجود است.

ب) افزودن المان base به ابتدای صفحه:
 <!DOCTYPE html>
<html>
<head>
    <base href="/">
بلافاصله پس از تگ head، المان base اضافه می‌شود. این المان به سیستم مسیریابی اعلام می‌کند که چگونه آدرس‌های خود را تشکیل دهد. به صورت پیش فرض، AngularJS 2.0 از آدرس‌هایی با فرمت HTML5 استفاده می‌کند. در این حالت دیگر نیازی به ذکر # برای مشخص سازی مسیریابی‌های محلی نیست.
از آنجائیکه فایل index.html در ریشه‌ی سایت قرار گرفته‌است، مقدار آغازین href آن به / تنظیم شده‌است.

ج) شبیه به حالت ثبت پروایدر HTTP در قسمت قبل، برای ثبت پروایدر مسیریابی نیز به فایل App\app.component.ts مراجعه می‌کنیم:
//same as before ...
import { ROUTER_PROVIDERS } from 'angular2/router';
 
//same as before ... 

@Component({
//same as before …
    providers: [
        ProductService,
        HTTP_PROVIDERS,
        ROUTER_PROVIDERS
    ]
})
//same as before ...
در اینجا سرویس ROUTER_PROVIDERS به خاصیت providers اضافه شده‌است و همچنین import متناظر با آن نیز به ابتدای صفحه اضافه گردیده‌است.
علت ختم شدن نام این سرویس‌ها به PROVIDERS این است که این تعاریف، امکان استفاده‌ی از چندین سرویس زیر مجموعه‌ی آن‌ها را فراهم می‌کنند و صرفا یک سرویس نیستند.


ساخت کامپوننت نمایش جزئیات محصولات

در ادامه می‌خواهیم جزئیات هر محصول را با کلیک بر روی نام آن در لیست محصولات، در آدرسی دیگر به صورتی مجزا مشاهده کنیم. به همین منظور به پوشه‌ی products برنامه مراجعه کرده و دو فایل جدید product-detail.component.ts و product-detail.component.html را ایجاد کنید؛ با این محتوا:
الف) محتوای فایل product-detail.component.html
<div class='panel panel-primary'>
    <div class='panel-heading'>
        {{pageTitle}}
    </div>
</div>
ب) محتوای فایل product-detail.component.ts
import { Component } from 'angular2/core';

@Component({
    templateUrl: 'app/products/product-detail.component.html'
})
export class ProductDetailComponent  {
    pageTitle: string = 'Product Detail'; 
}
در اینجا یک کامپوننت جدید را ایجاد کرده‌ایم که در قالب آن، مقدار خاصیت pageTitle با روش interpolation در آن درج شده‌است. کلاس ProductDetailComponent، قالب خود را از طریق مقدار دهی آدرس آن در خاصیت templateUrl متادیتای خود، پیدا می‌کند.
اگر دقت کنید، این کامپوننت ویژه دارای خاصیت selector نیست. ذکر selector تنها زمانی اجباری است که بخواهیم این کامپوننت را داخل کامپوننتی دیگر قرار دهیم. در اینجا قصد داریم این کامپوننت را به صورت یک View جدید، توسط سیستم مسیریابی نمایش دهیم و نه به صورت جزئی از یک کامپوننت دیگر.


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

مسیریابی در AngularJS 2.0، مبتنی بر کامپوننت‌ها است. به همین جهت، ابتدای کار مسیریابی، مشخص سازی تعدادی از کامپوننت‌ها هستند که قرار است به عنوان مقصد سیستم راهبری (navigation) مورد استفاده قرار گیرند و به ازای هر کدام، یک مسیریابی و Route جدید را تعریف می‌کنیم. تعریف هر Route جدید شامل انتساب نامی به آن، تعیین مسیر مدنظر و مشخص سازی کامپوننت مرتبط است:
 { path: '/products', name: 'Products', component: ProductListComponent },
برای مثال جهت تعریف Route کامپوننت لیست محصولات، نام آن‌را Products، مسیر آن‌را products/ و در آخر کامپوننت آن‌را به نام کلاس متناظر با آن، تنظیم می‌کنیم.
این تنظیمات به عنوان یک متادیتای جدید دیگر به کلاس AppComponent، در فایل app.component.ts اضافه می‌شوند:
//same as before …
import { ROUTER_PROVIDERS, RouteConfig } from 'angular2/router';
 
//same as before …
 
@Component({
    //same as before …
})
@RouteConfig([
    { path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true },
    { path: '/products', name: 'Products', component: ProductListComponent }
])
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
در اینجا decorator جدیدی به نام RouteConfig به کلاس AppComponent اضافه شده‌است و همچنین importهای متناظری نیز در ابتدای این فایل تعریف شده‌اند.
همانطور که ملاحظه می‌کنید، یک کلاس می‌تواند بیش از یک decorator داشته باشد.
()RouteConfig@ را به کامپوننتی الصاق می‌کنیم که قصد میزبانی مسیریابی را دارد (Host component). این مزین کننده، آرایه‌ای از اشیاء را قبول می‌کند و هر شیء آن دارای خواصی مانند مسیر، نام و کامپوننت است. باید دقت داشت که نام هر مسیریابی تعریف شده باید pascal case باشد. در غیراینصورت مسیریاب ممکن است این نام را با path اشتباه کند.  
همچنین امکان تعریف خاصیت دیگری به نام useAsDefault نیز در اینجا میسر است. از آن جهت تعریف مسیریابی پیش فرض سیستم، در اولین بار نمایش آن، استفاده می‌شود.

در اینجا نام کامپوننت، رشته‌ای ذکر نمی‌شود و دقیقا اشاره دارد به نام کلاس متناظر. بنابراین هر نام کلاسی که در اینجا اضافه می‌شود، باید به همراه import ماژول آن نیز در ابتدای فایل جاری باشد. به همین جهت اگر تنظیمات فوق را اضافه کنید، ذیل کلمه‌ی WelcomeComponent یک خط قرمز مبتنی بر عدم تعریف آن کشیده می‌شود. برای تعریف آن، پوشه‌ی جدیدی را به ریشه‌ی سایت به نام home اضافه کنید و به آن دو فایل ذیل را اضافه نمائید:
الف) محتوای فایل welcome.component.ts
import { Component } from 'angular2/core';
 
@Component({
    templateUrl: 'app/home/welcome.component.html'
})
export class WelcomeComponent {
    public pageTitle: string = "Welcome";
}
ب) محتوای فایل welcome.component.html
<div class="panel panel-primary">
    <div class="panel-heading">
        {{pageTitle}}
    </div>
    <div class="panel-body">
        <h3 class="text-center">Default page</h3>
    </div>
</div>
کار این کامپوننت، نمایش صفحه‌ی آغازین برنامه است؛ بر اساس تنظیم useAsDefault: true مسیریابی‌های تعریف شده‌.
پس از تعریف این کامپوننت، اکنون باید import ماژول آن‌را به ابتدای فایل app.component.ts اضافه کنیم، تا مشکل عدم شناسایی نام کلاس WelcomeComponent برطرف شود:
 import { WelcomeComponent } from './home/welcome.component';


فعال سازی مسیریابی‌های تعریف شده

روش‌های مختلفی برای دسترسی به اجزای یک برنامه وجود دارند؛ برای مثال کلیک بر روی یک لینک، دکمه و یا تصویر و سپس فعال سازی مسیریابی متناظر با آن. همچنین کاربر می‌تواند آدرس صفحه‌ای را مستقیما در نوار آدرس‌های مرورگر وارد کند. به علاوه امکان کلیک بر روی دکمه‌های back و forward مرورگر نیز همواره وجود دارند. تنظیمات مسیریابی‌های انجام شده، دو مورد آخر را به صورت خودکار مدیریت می‌کنند. در اینجا تنها باید مدیریت اولین حالت ذکر شده را با اتصال مسیریابی‌ها به اعمال کاربران، انجام داد.
به همین جهت منویی را به بالای صفحه‌ی برنامه اضافه می‌کنیم. برای این منظور، فایل app.component.ts را گشوده و خاصیت template کامپوننت AppComponent را به نحو ذیل تغییر می‌دهیم:
@Component({
    //same as before …
    template: `
     <div>
        <nav class='navbar navbar-default'>
            <div class='container-fluid'>
                <a class='navbar-brand'>{{pageTitle}}</a>
                <ul class='nav navbar-nav'>
                    <li><a [routerLink]="['Welcome']">Home</a></li>
                    <li><a [routerLink]="['Products']">Product List</a></li>
                </ul>
            </div>
        </nav>
        <div class='container'>
            <router-outlet></router-outlet>
        </div>
     </div>
    `,
    //same as before …
})
در اینجا یک navigation bar بوت استرپ 3، جهت تعریف منوی بالای صفحه اضافه شده‌است.
سپس جهت تعریف لینک‌های هر آیتم، از یک دایرکتیو توکار AngularJS 2.0 به نام routerLink استفاده می‌کنیم. هر routerLink به یکی از آیتم‌های تنظیم شده‌ی در RouteConfig بایند می‌شود. بنابراین نام‌هایی که در اینجا قید شده‌اند، دقیقا نام‌هایی هستند که در خاصیت name هر کدام از اشیاء تشکیل دهنده‌ی RouteConfig، تعریف و مقدار دهی گردید‌ه‌اند.
اکنون اگر کاربر بر روی یکی از لینک‌های Home و یا Product List کلیک کند، مسیریابی متناظر با آن فعال می‌شود (بر اساس این نام، در لیست عناصر RouteConfig جستجویی صورت گرفته و عنصر معادلی بازگشت داده می‌شود) و سپس View آن کامپوننت نمایش داده خواهد شد.
تا اینجا دایرکتیو جدید routerLink به قالب کامپوننت اضافه شد‌ه‌است؛ اما AngularJS 2.0 نمی‌داند که باید آن‌را از کجا دریافت کند. به همین جهت ابتدا import آن‌را (ROUTER_DIRECTIVES) به ابتدای ماژول جاری اضافه خواهیم کرد:
 import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
و سپس خاصیت دایرکتیوهای کامپوننت ریشه‌ی سایت را نیز با آن مقدار دهی خواهیم کرد:
 directives: [ROUTER_DIRECTIVES],
علت جمع بود نام این دایرکتیو این است که routerLink استفاده شده، یکی از اعضای مجموعه‌ی دایرکتیوهای مسیریابی توکار AngularJS 2.0 است.

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

در انتهای قالب کامپوننت ریشه‌ی سایت، یک دایرکتیو جدید به نام router-outlet نیز تعریف شده‌است. وقتی یک کامپوننت فعال می‌شود، نیاز است View مرتبط با آن نیز نمایش داده شود. دایرکتیو router-outlet محل نمایش این View را مشخص می‌کند.

اکنون اگر برنامه را اجرا کنیم، به این شکل خواهیم رسید:


اگر دقت کنید، آدرس بالای صفحه، در اولین بار نمایش آن به http://localhost:2222/welcome تنظیم شده و این مقدار دهی بر اساس خاصیت useAsDefault تنظیمات مسیریابی سایت انجام شده‌است (نمایش welcome به عنوان صفحه‌ی اصلی و پیش فرض).
همچنین با کلیک بر روی لینک لیست محصولات، کامپوننت آن فعال شده و نمایش داده می‌شود. محل قرارگیری این کامپوننت‌ها، دقیقا در محل قرارگیری دایرکتیو router-outlet است.


ارسال پارامترها به سیستم مسیریابی

در ابتدا بحث، مقدمات کامپوننت نمایش جزئیات یک محصول انتخابی را تهیه کردیم. برای فعال سازی این کامپوننت و مسیریابی آن، نیاز است بتوان پارامتری را به سیستم مسیریابی ارسال کرد. برای مثال با انتخاب آدرس product/5، جزئیات محصول با ID مساوی 5 نمایش داده شود.
برای این منظور:
الف) اولین قدم، تعریف مسیریابی آن است. به همین جهت به فایل app.component.ts مراجعه و دو تغییر ذیل را به آن اعمال کنید:
//same as before …

import { ProductDetailComponent } from './products/product-detail.component';
 
@Component({
    //same as before …
})
@RouteConfig([
    //same as before …
    { path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent }
])
//same as before …
ابتدا مسیریابی جدیدی به نام ProductDetail اضافه شده‌است و سپس ماژول دربرگیرنده‌ی کلاس کامپوننت آن نیز import شده‌است.
تفاوت این مسیریابی با نمونه‌های قبلی در تعریف id:/ است. پس از ذکر :/، نام یک متغیر عنوان می‌شود و اگر نیاز به چندین متغیر بود، همین الگو را تکرار خواهیم کرد.

ب) سپس نحوه‌ی فعال سازی این مسیریابی را توسط تعریف لینکی جدید، معرفی می‌کنیم. بنابراین فایل قالب product-list.component.html را گشوده و سپس بجای نمایش عنوان محصول:
 <td>{{ product.productName }}</td>
لینک به جزئیات آن‌را نمایش می‌دهیم:
<td>
    <a [routerLink]="['ProductDetail', {id: product.productId}]">
        {{product.productName}}
    </a>
</td>
نحوه‌ی تعریف این لینک، با لینک‌هایی که در منوی بالای سایت اضافه کردیم، یکی است؛ با این تفاوت که اکنون پارامتر دومی را به قسمت یافتن نام این Route، جهت مشخص سازی روش مقدار دهی متغیر id، تعریف کرده‌ایم. در اینجا id هر لینک از productId بایند شده تامین می‌شود.
اکنون که از دایرکتیو جدید routerLink در این قالب استفاده شده‌است، نیاز است تعریف دایرکتیو آن‌را به متادیتای کلاس کامپوننت لیست محصولات نیز اضافه کنیم تا AngularJS 2.0 بداند آن‌را از کجا باید تامین کند:
import { Component, OnInit } from 'angular2/core';
import { ROUTER_DIRECTIVES } from 'angular2/router';
//same as before …
 
@Component({
    //same as before …
    directives: [StarComponent, ROUTER_DIRECTIVES]
})

در ادامه اگر برنامه را اجرا کنید، عنوان‌های محصولات، به آدرس نمایش جزئیات آن‌ها لینک شده‌اند:


ج) در آخر زمانیکه View نمایش جزئیات محصول فعال می‌شود، نیاز است این id را از url جاری دریافت کند. به همین جهت فایل product-detail.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core';
import { RouteParams } from 'angular2/router';
 
@Component({
    templateUrl: 'app/products/product-detail.component.html'
})
export class ProductDetailComponent {
    pageTitle: string = 'Product Detail';
 
    constructor(private _routeParams: RouteParams) {
        let id = +this._routeParams.get('id');
        this.pageTitle += `: ${id}`;
    } 
}
با نحوه‌ی تزریق وابستگی‌ها در قسمت هفتم آشنا شدیم. در اینجا سرویس توکار RouteParams به سازنده‌ی کلاس تزریق شده‌‌است. با استفاده از آن می‌توان به id ارسالی از طریق url دسترسی یافت. در اینجا پارامتری که به متد get ارسال می‌شود، باید با نام پارامتری که در تنظیمات آغازین مسیریابی سیستم تعریف گردید، تطابق داشته باشد.
در این حالت، id دریافتی، به متغیر pageTitle اضافه شده و در قالب مربوطه به صورت خودکار نمایش داده می‌شود.

تا اینجا اگر برنامه را اجرا کنید، صفحه‌ی نمایش جزئیات یک محصول، با کلیک بر روی عناوین آن‌ها به صورت زیر نمایش داده می‌شود:



افزودن دکمه‌ی back با کدنویسی

اکنون برای بازگشت مجدد به لیست محصولات، می‌توان از دکمه‌ی back مرورگر استفاده کرد، اما امکان طراحی این دکمه در قالب‌ها نیز پیش بینی شده‌است.
برای این منظور قالب product-detail.component.html را به نحو ذیل بازنویسی می‌کنیم:
<div class='panel panel-primary'>
    <div class='panel-heading'>
        {{pageTitle}}
    </div>
    <div class='panel-footer'>
        <a class='btn btn-default' (click)='onBack()' style='width:80px'>
            <i class='glyphicon glyphicon-chevron-left'></i> Back
        </a>
    </div>    
</div>
در اینجا دکمه‌ی بازگشت به صفحه‌ی قبلی اضافه شده‌است که به متد onBack در کلاس مرتبط با این قالب، متصل است.

سپس کدهای product-detail.component.ts را به صورت ذیل تکمیل خواهیم کرد:
import { Component } from 'angular2/core';
import { RouteParams, Router } from 'angular2/router';
 
@Component({
    templateUrl: 'app/products/product-detail.component.html'
})
export class ProductDetailComponent {
    pageTitle: string = 'Product Detail';
 
    constructor(private _routeParams: RouteParams, private _router: Router) {
        let id = +this._routeParams.get('id');
        this.pageTitle += `: ${id}`;
    }
 
    onBack(): void {
        this._router.navigate(['Products']);
    }
}
در اینجا سرویس جدیدی به نام Router در سازنده‌ی کلاس تزریق شده‌است. این سرویس امکان فراخوانی متدهایی مانند navigate را جهت حرکت به مسیریابی مشخصی، میسر می‌کند. پارامتری که به این متد ارسال می‌شود، دقیقا معادل همان پارامتری است که به دایرکتیو routerLink ارسال می‌گردد و در اینجا صرفا نام یک مسیریابی مشخص شده‌است؛ بدون ذکر پارامتری.


رفع تداخل مسیریابی‌های ASP.NET MVC با مسیریابی‌های AngularJS 2.0

در طی بحث جاری عنوان شد که اگر کاربر مسیر http://localhost:2222/product/2 را جایی ثبت کرده یا bookmark کند، پس از فراخوانی مستقیم آن در نوار آدرس‌های مرورگر، بلافاصله به این آدرس هدایت خواهد شد. این مورد صحیح است اگر از index.html بجای بکارگیری ASP.NET MVC، جهت هاست برنامه استفاده شود. اگر چنین آدرسی را در یک برنامه‌ی ASP.NET MVC فراخوانی کنیم، ابتدا به دنبال کنترلری به نام product می‌گردد (ابتدا وارد موتور ASP.NET MVC می‌شود) و چون این کنترلر در سمت سرور تعریف نشده‌است، پیام 404 و یا یافت نشد را مشاهده خواهید کرد و فرصت به اجرای برنامه‌ی AngularJS نخواهد رسید.
برای حل این مشکل نیاز است یک route جدید را به نام catch all، در انتهای مسیریابی‌های فعلی اضافه کنید؛ تا سایر درخواست‌های رسیده را به صفحه‌ی نمایش برنامه‌ی AngularJS هدایت کند:
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            constraints: new { controller = "Home" } // for catch all to work, Home|About|SomeName
        );
 
        // Route override to work with Angularjs and HTML5 routing
        routes.MapRoute(
            name: "NotFound",
            url: "{*catchall}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}


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


خلاصه‌ی بحث

حین ایجاد کامپوننت‌ها باید به نحوه‌ی نمایش آن‌ها نیز فکر کرد. اگر کامپوننتی قرار است داخل یک کامپوننت دیگر نمایش یابد، باید دارای selector باشد. یک چنین کامپوننتی نیاز به تعریف مسیریابی ندارد. برای کامپوننت‌هایی که به عنوان یک View مستقل طراحی می‌شوند و قرار است در یک صفحه‌ی مجزا نمایش داده شوند، نیازی به تعریف selector نیست؛ اما باید برای آن‌ها مسیریابی‌های ویژه‌ای را تعریف کرد. همچنین نیاز است مدیریت اعمال کاربران را جهت فعال سازی آن‌ها نیز مدنظر داشت. برای استفاده از امکانات مسیریابی توکار AngularJS 2.0 نیاز است اسکریپت آن‌را به صفحه‌ی اصلی اضافه کرد. سپس باید المان base را جهت نحوه‌ی تشکیل آدرس‌های مسیریابی، به صفحه اضافه کرد. در ادامه کار ثبت ROUTER_PROVIDERS در بالاترین سطح سلسه مراتب کامپوننت‌های سایت انجام می‌شود. با استفاده از RouteConfig کار تنظیمات ابتدایی مسیریابی صورت خواهد گرفت. این decorator به کامپوننتی که قرار است کار میزبانی مسیریابی را انجام دهد، متصل می‌شود. پس از تعریف مسیریابی‌ها با ذکر یک نام منحصربفرد، مسیری که باید توسط کاربر وارد شود و نام کامپوننت مدنظر، با استفاده از دایرکتیو routerLink کار تعریف این آدرس‌ها، در رابط کاربری برنامه انجام می‌شود. این دایرکتیو جدید، جزئی از مجموعه‌ی ROUTER_DIRECTIVES است که باید به لیست دایرکتیوهای کامپوننت ریشه‌های سایت و هر کامپوننتی که از routeLink استفاده می‌کند، اضافه شود. برای بایند این دایرکتیو به مسیریابی‌های تعریف شده، سمت راست این اتصال باید به آرایه‌ای از مقادیر مقدار دهی شود که اولین عنصر آن، نام یکی از عناصر مسیریابی تعریف شده‌ی در RouteConfig است. از دومین عنصر آن برای مقدار دهی پارامترهای ارسالی به سیستم مسیریابی استفاده می‌شود. کار دایرکتیو router-outlet، مشخص سازی محل نمایش یک View است که عموما در قالب میزبان مسیریابی قرار می‌گیرد. برای تعیین پارامترهای مسیریابی، از الگوی paramName:/ استفاده می‌شود. برای دسترسی به این مقادیر در یک کامپوننت، می‌توان از سرویس RouteParams استفاده کرد. برای فعال سازی یک مسیریابی با کدنویسی، از سرویس Router و متد navigate آن کمک می‌گیریم.
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 5 - آشنایی با کلاس ServiceDescriptor
در بخش پنجم از سری نوشتار «تزریق وابستگی‌ها در ASP.NET Core»، می‌خواهیم به شرح کلاس ServiceDescriptor بپردازیم. اگر تعریف اینترفیس IServiceCollection را مشاهده کنیم، می‌بینیم که IServicecollection در واقع لیستی از اشیائی از نوع ServiceDescriptor را نگهداری می‌کند:
namespace Microsoft.Extensions.DependencyInjection
{
    public interface IServiceCollection : 
       ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>,
       IEnumerable, IList<ServiceDescriptor>
    {
    }
}

ServiceProvider و مؤلفه‌های درونی آن، از یک مجموعه از ServiceDescriptor‌ها برای برنامه‌ی شما بر اساس سرویس‌های ثبت شده‌ی توسط IServiceCollection استفاده می‌کنند. ServiceDescriptor حاوی اطلاعاتی در مورد سرویس‌های ثبت شده‌است. اگر به کد منبع این کلاس برویم، می‌بینیم پنج Property اصلی دارد که با استفاده از آن‌ها اطلاعات یک سرویس ثبت و نگهداری می‌شوند. با استفاده از این  اطلاعات در هنگام اجرا ، DI Container به واکشی و ساخت نمونه‌هایی از سرویس درخواستی اقدام می‌کند:

public Type ImplementationType { get; }
public object ImplementationInstance { get; }
public Func<IServiceProvider, object> ImplementationFactory { get; }
public ServiceLifetime Lifetime { get; }
public Type ServiceType { get; }

هر کدام از این Property ‌ها کاربرد خاص خود را دارند:

  • · ServiceType : نوع سرویسی را که می‌خواهیم ثبت شود، مشخص می‌کنیم ( مثلا اینترفیس IMessageService ) .  
  • · ImplementionType : نوع پیاده سازی سرویس مورد نظرمان را مشخص می‌کند ( مثلا کلاس MessageService ).  
  • · LifeTime : طول حیات سرویس را مشخص می‌کند. DI Container بر اساس این ویژگی، اقدام به ساخت و از بین بردن نمونه‌هایی از سرویس می‌کند.  
  • · ImplementionInstance : نمونه‌ی ساخته شده‌ی از سرویس است.  
  • · ImplementionFactory : یک Delegate است که چگونگی ساخته شدن یک نمونه از پیاده سازی سرویس را در خود نگه می‌دارد. این Delegate یک IServiceProvider را به عنوان ورودی دریافت می‌کند و یک object را بازگشت می‌دهد.

به صورت عادی، در سناریوهای معمول ثبت سرویس‌ها درون IServiceCollection، نیازی به استفاده از ServiceDescriptor نیست؛ ولی اگر بخواهیم سرویس‌ها را به روش‌های پیشرفته‌تری ثبت کنیم، مجبوریم که به صورت مستقیم با این کلاس کار کنیم.

 

می توانیم یک ServiceDesciriptor را به روش‌های زیر تعریف کنیم:

var serviceDescriptor1 = new ServiceDescriptor(
   typeof(IMessageServiceB),
   typeof(MessageServiceBB),
   ServiceLifetime.Scoped);

var serviceDescriptor2 = ServiceDescriptor.Describe(
   typeof(IMessageServiceB),
   typeof(MessageServiceBB),
   ServiceLifetime.Scoped);

var serviceDescriptor3 = ServiceDescriptor.Singleton(typeof(IMessageServiceB), typeof(MessageServiceBB));

var serviceDescriptor4 = ServiceDescriptor.Singleton<IMessageServiceB, MessageServiceBB>();
در بالا روش‌های تعریف یک ServiceDescriptor را می‌بینید. اولین متد و تعریف پارامترها در سازنده‌ها، روش پایه است؛ ولی برای راحتی کار، توسعه دهندگان تعدادی متد static نیز تعریف کرده‌اند که خروجی آنها یک نمونه از ServiceDescriptor است.

همانطور که دیدیم، IServiceCollection در واقع لیست و مجموعه‌ای از اشیاء است که از نمونه‌های جنریک IServiceCollection ، IList ، IEnumerable و Ienumberabl ارث بری می‌کند؛ بنابراین می‌توان از متدهای تعریف شده‌ی در این اینترفیس‌ها برای IServiceCollection نیز استفاده کرد. حالا ما برای اضافه کردن این سرویس‌های جدید، بدین طریق عمل می‌کنیم:

Services.Add(serviceDescriptor1);

استفاده از متدهای TryAdd() 

به کد زیر نگاه کنید :

services.AddScoped<IMessageServiceB, MessageServiceBA>();
services.AddScoped<IMessageServiceB, MessageServiceBB>();
همانطور که می‌بینید، در اینجا یک اینترفیس را دوبار ثبت کردیم. در این حالت موقع واکشی سرویس، DI Container آخرین نمونه‌ی ثبت شده‌ی برای اینترفیس را واکشی کرده و نمونه سازی می‌کند و به کلاس‌ها تزریق می‌کند. این یکی از مواردی است که ترتیب ثبت کردن سرویس‌های مهم است.

برای جلوگیری از این خطا می‌توانیم از متدهای TryAddSingleton() ، TryAddScoped() و TryAddTransient() استفاده کنیم. این متدها درون فضای نام Microsoft.Extionsion.DependencyInjection.Extension قرار دارند.

عملکرد کلی این متدها درست مثل متد‌های Add() است؛ با این تفاوت که این متد ابتدا IServiceCollection را جستجو می‌کند و اگر برای type مورد نظر سرویسی ثبت نشده بود، آن را ثبت می‌کند:

services.TryAddScoped<IMessageServiceB, MessageServiceBA>();
services.TryAddScoped<IMessageServiceB, MessageServiceBB>();

جایگذاری یک سرویس با نمونه‌ای دیگر

گاهی اوقات می‌خواهیم یک پیاده سازی دیگر را بجای پیاده سازی فعلی، در DI Container ثبت کنیم. در این حالت از متد Replace() بر روی IServiceCollection برای این کار استفاده می‌کنیم. این متد فقط یک ServiceDescriptor را به عنوان پارامتر ورودی می‌گیرد:

services.Replace(serviceDescriptor3);
ناگفته نماند که متد Replace() فقط اولین سرویس را با نمونه‌ی مورد نظر ما جایگذاری می‌کند. اگر می‌خواهید تمام نمونه سرویس‌های ثبت شده را برای یک نوع حذف کنید، می‌توانید از متد RemoveAll() استفاده کنید:
services.RemoveAll<IMessageServiceB>();

معمولا در پروژه‌های معمول خودمان نیازی به استفاده از Replace() و RemoveAll() نداریم؛ مگر اینکه بخواهیم پیاده سازی اختصاصی خودمان را برای سرویس‌های درونی فریم ورک یا کتابخانه‌های شخص ثالث، بجای پیاده سازی پیش فرض، ثبت و استفاده کنیم.  

 

AddEnumerable()

فرض کنید دارید برنامه‌ی نوبت دهی یک کلینیک را می‌نویسید و به صورت پیش فرض از شما خواسته‌اند که هنگام صدور نوبت، این قوانین را بررسی کنید:

  •   هر شخص در هفته نتواند بیش از 2 نوبت برای یک تخصص بگیرد.
  •   اگر شخص در ماه بیش از 3 نوبت رزرو شده داشته باشد ولی مراجعه نکرده باشد، تا پایان ماه، امکان رزرو نوبت را نداشته باشد .
  •   تعداد نوبت‌های ثبت شده‌ی برای پزشک در آن روز نباید بیش از تعدادی باشد که پزشک پذیرش می‌کند.
  •   و ...

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

  •   نوبت‌های ثبت شده برای یک شخص نباید دارای تداخل باشند.

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

public class RegisterAppointmentService : RegisterAppointmentService
{
  public Task<Result> RegisterAsync(
    PatientInfoDTO patientIfno , DateTimeOffset requestedDateTime ,
    PhysicianId phusicianId )
  {
      CheckRegisterantionRule(patientInfo);
      // code here
  }

  private Task CheckRegisterationRule(PatientInfoDTO patientInfo)
  {
       CheckRule1(patientInfo);
       CheckRule2(patientInfo);
       CheckRule3(patientInfo);
  }
}  

در این حالت باید به ازای هر قانون جدید، به متد CheckRegisterationRule برویم و به ازای هر قانون، یک متد private جدید را بسازیم. مشکل این روش این است که در این حالت ما مجبوریم با هر کم و زیاد شدن قانون، این کلاس را باز کنیم و آن را تغییر دهیم و با هر تغییر دوباره، تست‌های واحد آن را دوباره نویسی کنیم. در یک کلام در کد بالا اصول Separation of Concern و  Open/Closed Principle را رعایت نمی‌شود.

یک راهکار این است که یک سرویس جداگانه را برای بررسی کردن قوانین بنویسیم و آن را به سرویس ثبت نوبت تزریق کنیم:

public class ICheckRegisterationRuleForAppointmentService : ICheckRegisterationRuleForAppointmentService
{
     public Task CheckRegisterantionRule(PatientInfoDTO patientInfo)
     {
                CheckRule1(patientInfo);
                CheckRule2(patientInfo);
                CheckRule3(patientInfo);
      }
}

public class RegisterAppointmentService : IRegisterAppointmentService
{
  private ICheckRegisterationRuleForAppointmentService  _ruleChecker;
 
  public RegisterAppointmentService (RegisterAppointmentService  ruleChecker)
  {
          _ruleChecker = ruleChecker;  
  }

  public Task<Result> RegisterAsync(
     PatientInfoDTO patientIfno , 
     DateTimeOffset requestedDateTime , 
     PhysicianId phusicianId )
  {
             _ruleChecker.CheckRegisterantionRule(patientInfo);
                // code here
  }
}

با این کار وظیفه‌ی چک کردن قوانین و وظیفه‌ی ثبت و ذخیره سازی قوانین را از یکدیگر جدا کردیم؛ ولی همچنان در سرویس بررسی کردن قوانین، اصل Open/Closed رعایت نشده‌است. خب راه حل چیست !؟

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


interface IAppointmentRegisterationRule
{
  Task CheckRule(PatientInfo patientIfno);
}

public class AppointmentRegisterationRule1 : IAppointmentRegisterationRule
{
      public Task CheckRule(PatientInfo patientIfno)
      {
          Console.WriteLine("Rule 1 is checked");
          return Task.CompletedTask;
      }
}

public class AppointmentRegisterationRule2 : IAppointmentRegisterationRule
{
     public Task CheckRule(PatientInfo patientIfno)
     {
Console.WriteLine("Rule 2 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule3 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 3 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule4 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 4 is checked"); return Task.CompletedTask; } }
حالا که ما قوانین خودمان را تعریف کردیم، به روش زیر می‌توانیم آن‌ها را درون سازنده ثبت کنیم:
services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule1>();
services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule2>();
services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule3>();
services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule4>();
حالا می‌توانیم درون سازنده‌ی سرویس مورد نظرمان، لیستی از سرویس‌های ثبت شده‌ی برای یک نوع خاص را به با استفاده از اینترفیس جنریک IEnumerable<T> دریافت کنیم که در اینجا T، برابر نوع سرویس مورد نظرمان است:
public class CheckRegisterationRuleForAppointmentService : ICheckRegisterationRuleForAppointmentService
{
       private IEnumerable<IAppointmentRegisterationRule> _rules ;

       public CheckRegisterationRuleForAppointmentService(IEnumerable<IAppointmentRegisterationRule> rules)
       {
           _rules = rules;
       }

      public Task CheckRegisterantionRule(PatientInfoDTO patientInfo)
      {
          foreach(var rule in rules)
          {
                rule.CheckRule(patientInfo);
          }
      }
}
با این تغییرات، هر زمانیکه خواستیم می‌توانیم با استفاده از DI Container، قوانین جدیدی را اضافه یا کم کنیم و با این کار، اصل Open/Closed را نیز رعایت کرده‌ایم.

 کد بالا به نظر کامل می‌آید ولی مشکلی دارد! اگر در DI Container برای IAppointmentRegisterationRule یک قانون را دو یا چند بار ثبت کنیم، در هر بار بررسی کردن قوانین، آن را به همان تعداد بررسی می‌کند و اگر این فرآیند منابع زیادی را به کار می‌گیرد، می‌تواند عملکرد برنامه‌ی ما را به هم بریزد.  برای جلوگیری از این مشکل، از متد TryAddEnumerabl() استفاده می‌کنیم که لیستی از ServiceDescriptor ‌ها را می‌گیرد و هر serviceDescriptor را فقط یکبار ثبت می‌کند:

services.TryAddEnumerable(new[] {
  ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule1)),
  ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule2)),
  ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule3)),
  ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule4)),
});

مطالب
OpenCVSharp #4
کار با فیلترها در OpenCVSharp

فرض کنید قصد داریم یک چنین مثال زبان C را که در مورد کار با فیلترها در OpenCV است، به نمونه‌ی دات نتی آن تبدیل کنیم:
        #include <cv.h>
        #include <highgui.h>
        #include <stdio.h>
 
        int main (int argc, char **argv)
        {
          IplImage *src_img = 0, *dst_img;
          float data[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
          };
          CvMat kernel = cvMat (1, 21, CV_32F, data);
 
          if (argc >= 2)
            src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
          if (src_img == 0)
            exit (-1);
 
          dst_img = cvCreateImage (cvGetSize (src_img), src_img->depth, src_img->nChannels);
 
          cvNormalize (&kernel, &kernel, 1.0, 0, CV_L1);
          cvFilter2D (src_img, dst_img, &kernel, cvPoint (0, 0));
 
          cvNamedWindow ("Filter2D", CV_WINDOW_AUTOSIZE);
          cvShowImage ("Filter2D", dst_img);
          cvWaitKey (0);
 
          cvDestroyWindow ("Filter2D");
          cvReleaseImage (&src_img);
          cvReleaseImage (&dst_img);
 
          return 0;
        }
معادل OpenCVSharp آن به صورت ذیل خواهد بود:
using (var src = new IplImage(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
using (var dst = new IplImage(src.Size, src.Depth, src.NChannels))
{
    float[] data = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
    var kernel = new CvMat(rows: 1, cols: 21, type: MatrixType.F32C1, elements: data);
 
    Cv.Normalize(src: kernel, dst: kernel, a: 1.0, b: 0, normType: NormType.L1);
    Cv.Filter2D(src, dst, kernel, anchor: new CvPoint(0, 0));
 
    using (new CvWindow("src", image: src))
    using (new CvWindow("dst", image: dst))
    {
        Cv.WaitKey(0);
    }
}
با این خروجی:


در قسمت‌های قبلی در مورد بارگذاری تصاویر، تهیه‌ی یک Clone از آن و همچنین ساخت یک پنجره به روش‌های مختلف و رها سازی خودکار منابع مرتبط، بیشتر بحث شد. در اینجا تصویر اصلی با همان عمق و وضوح تغییر نیافته‌ی آن بارگذاری می‌شود.
کار cvMat، آغاز یک ماتریس OpenCV است. پارامترهای آن، تعداد ردیف‌ها، ستون‌ها، نوع داده‌ی المان‌ها و داده‌های مرتبط را مشخص می‌کنند.
در این مثال آرایه‌ی data، یک فیلتر را تعریف می‌کند که در اینجا، حالت یک بردار را دارد تا یک ماتریس. برای تبدیل آن به ماتریس، از شیء CvMat استفاده خواهد شد که آن‌را تبدیل به ماتریسی با یک ردیف و 21 ستون خواهد کرد.
در اینجا از نام کرنل استفاده شده‌است. کرنل در OpenCV به معنای ماتریسی از داده‌ها با یک نقطه‌ی anchor (لنگر) است. این لنگر به صورت پیش فرض در میانه‌ی ماتریس قرار دارد (نقطه‌ی 1- , 1- ).


مرحله‌ی بعد، نرمال سازی این فیلتر است. تاثیر نرمال سازی اطلاعات را به این نحو می‌توان نمایش داد:
 double sum = 0;
foreach (var item in data)
{
     sum += Math.Abs(item);
}
Console.WriteLine(sum); // => .999999970197678
در اینجا پس از نرمال سازی، جمع عناصر بردار data، تقریبا مساوی 1 خواهد بود و تمام عناصر بردار data، به داده‌هایی بین یک و صفر، نگاشت خواهند شد.
اگر مرحله‌ی نرمال سازی اطلاعات را حذف کنیم، تصویر نهایی حاصل، چنین شکلی را پیدا می‌کند:


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




فیلترهای توکار OpenCV

علاوه بر امکان طراحی فیلترهای سفارشی خطی مانند مثال فوق، کتابخانه‌ی OpenCV دارای تعدادی فیلتر توکار نیز می‌باشد که نمونه‌ای از آن‌را در مثال ذیل می‌توانید مشاهده کنید:
using (var src = new IplImage(@"..\..\Images\Car.jpg", LoadMode.AnyDepth | LoadMode.AnyColor))
{
    using (var dst = new IplImage(src.Size, src.Depth, src.NChannels))
    {
        using (new CvWindow("src", image: src))
        {
            Cv.Erode(src, dst);
            using (new CvWindow("Erode", image: dst))
            {
                Cv.Dilate(src, dst);
                using (new CvWindow("Dilate", image: dst))
                {
                    Cv.Not(src, dst);
                    using (new CvWindow("Invert", image: dst))
                    {
                        Cv.WaitKey(0);
                    }
                }
            }
        }
    }
}
با این خروجی



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
MSBuild
MSBuild
به عنوان یک تعریف کلی، مایکروسافت بیلد (Microsoft Build)، پلتفرمی برای ساخت اپلیکیشن‌هاست. در این پلتفرم (که با عنوان MSBuild شناخته میشود) کلیه تنظیمات لازم برای تولید و ساخت یک اپلیکیشن درون یک فایل XML ذخیره میشود، که به آن فایل پروژه میگویند. ویژوال استودیو نیز از این ابزار برای تولید تمامی اپلیکیشن‌ها استفاده می‌کند، اما MSBuild به ویژوال استودیو وابسته نیست و کاملا مستقل از آن است.
این ابزار به همراه دات نت فریمورک (البته نسخه کامل آن و نه نسخه‌های سبکتری چون Client Profile) نصب میشود. بنابراین با استفاه از فایل اجرایی این ابزار (msbuild.exe) میتوان فرایند بیلد را برای پروژه و یا سولوشن‌های خود، بدون نیاز به نصب ویژوال استودیو اجرا کرد. استفاده مستقیم از MSBuild در شرایط زیر نیاز میشود:
- ویزوال استودیو در دسترس نباشد.
- نسخه 64 بیتی این ابزار که در ویژوال استودیو در دسترس نیست. البته در بیشتر مواقع این مورد پیش نخواهد آمد مگر اینکه برای فرایند بیلد به حافظه بیشتری نیاز باشد.
- اجرای فرایند بیلد در بیش از یک پراسس (برای رسیدن به سرعت بالاتر). این امکان در تولید پروژه‌های ++C در ویژوال استودیو موجود است. همچنین از نسخه 2012 این امکان برای پروژه‌های #C نیز فراهم شده است.
- سفارشی‌سازی فرایند بیلد
- و ...
همچنین یکی دیگر از بخشهای مهم فرایندِ تولیدِ اپلیکیشن که همانند ویژوال استودیو از این ابزار بصورت مستقیم استفاده میکند Team Foundation Build است.
با استفاده از خط فرمان این ابزار تنظیمات فراوانی را برای سفارشی سازی عملیات بیلد میتوان انجام داد که شرح آنها بحثی مفصل میطلبد. تنظیمات بسیار دیگری هم در فایل پروژه قابل اعمال است (توضیحات بیشتر در اینجا). منابع برای مطالعه بیشتر:
 Microsoft Build API
در دات‌نت فریمورک فضای نامی با عنوان Microsoft.Build نیز وجود دارد که امکانات این ابزار را در اختیار برنامه نویس قرار میدهد. برای استفاده از این کتابخانه باید ارجاعی به اسمبلی آن داد، که به همین نام بوده و به همراه دات‌نت فریمورک نصب میشود. کد زیر نحوه استفاده اولیه از این کتابخانه را نشان میدهد:
private static void TestMSBuild(string projectFullPath)
{
  var pc = new ProjectCollection();
  var globalProperties = new Dictionary<string, string>() { { "Configuration", "Debug" }, { "Platform", "AnyCPU" } };
  var buidlRequest = new BuildRequestData(projectFullPath, globalProperties, null, new string[] { "Build" }, null);
  var buildResult = BuildManager.DefaultBuildManager.Build(new BuildParameters(pc), buidlRequest);
}
با اینکه ارائه مقداری غیرنال برای آرگومان globalProperties اجباری است اما پرکردن آن کاملا اختیاری است، زیرا تمام تنظیمات ممکن را میتوان در خود فایل پروژه ثبت کرد.
برای مطالعه بیشتر منابع زیر پیشنهاد میشود:
استفاده از msbuild.exe
ابزار msbuild به صورت یک فایل exe در دسترس است و برای استفاده از آن میتوان از خط فرمان ویندوز استفاده کرد. مسیر فایل اجرایی آن (MSBuild.exe) در ریشه مسیر دات نت فریمورک است، بصورت زیر:
نسخه 32 بیتی:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
نسخه 64 بیتی:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe
برای استفاده از آن میتوان مسیر فایل پروژه یا سولوشن (فایل با پسوند csprj. یا vbprj. یا sln.) را به آن داد تا سایر عملیات تولید را به صورت خودکار تا آخر به انجام برساند. کاری که عینا در ویژوال استودیو در زمان Build انجام میشود! برای بهره برداری از آن در کد میتوان از کلاس Process استفاده کرد. برای مسیر این فایل هم میتوان از نشانی‌هایی که در بالا معرفی شد استفاده کرد یا برای راحتی و امنیت بیشتر از کلید رجیستری مربوطه که در کد زیر نشان داده شده استفاده کرد:
private static void TestMSBuild1(string projectPath)
{
  var regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0");
  if (regKey == null) return;
  var msBuildExeFilePath = Path.Combine(regKey.GetValue("MSBuildToolsPath").ToString(), "MSBuild.exe");
  var startInfo = new ProcessStartInfo
                    {
                      FileName = msBuildExeFilePath,
                      Arguments = projectPath,
                      WindowStyle = ProcessWindowStyle.Hidden
                    };
  var process = Process.Start(startInfo);
  process.WaitForExit();
}
بدین ترتیب عملیاتی مشابه عملیات Build در ویژوال استودیو انجام میشود و با توجه به تنظیمات موجود در فایل پروژه، پوشه‌های خروجی (مثلا bin و obj در حالت پیش فرض پروژه‌های ویژوال استودیو) نیز در مسیرهای مربوطه ایجاد میگردد.
نظرات مطالب
تجربه‌ای ناخوشایند از اثر به روز رسانی‌ها بر روی TFS
با این مساله که مطرح کردید کاملا موافقم اما پیشنهاد می‌کنم اگرسیستم خوبی دارید و حافظه کافی در اختیار دارید این‌گونه امور را در ماشین مجازی انجام دهید و بعد از هرگونه تغییر جدی در برنامه‌های نصب شده، سیستم عامل را خاموش کرده و یک Snapshot از حالت اخیر سیستم عامل تهیه کنید تا در مواقعی که دچار مشکل شدید، از آن Snapshot استفاده کنید و احیاناً اگر خواستید این ماشین را به کس دیگری بدهید به راحتی می‌توانید یک Full Clone  از ماشین مد نظر در Snapshot دلخواه، تهیه کرده و از آن استفاده کنید. ضمنا به دو نکته توجه کنید که اولاً همیشه پروژه‌ها و اطلاعات مهم خود را در دیسک مجازی‌ای نگه‌داری کنید که با بازگرداندن Snapshot تحت تأثیر قرار نگیرد. ثانیاً خاموش کردن سیستم عامل مجازی به هنگام تهیه Snapshot به دلیل آن است که در هنگام تهیه Snapshot در مصرف فضای دیسک صرفه جویی شود.
مطالب
بازسازی LocalDb
LocalDb یک نسخه‌ی کوچک شده از SQL Express 2012 است که نیازی به سرویس ندارد که سبب سهولت استفاده از آن را در پروژه‌های Visual Studio  فراهم می‌آورد و یک ویژگی بسیار خوب برای برنامه‌هایی است که نیاز به استفاده از کلیه‌ی امکانات مهیای SQL Server را ندارند. یکی دیگر از ویژگی‌های آن استفاده در محیط هایی است که اطمینانی از نصب بودن SQL Express در آن‌ها نداریم.
در حین توسعه‌ی نرم افزار ممکن است استفاده از LocalDb امکان پذیر نباشد و خللی در کارکرد آن ایجاد شده باشد و عملا استفاده از آن میسر نشود. اتفاقی که برای من روز گذشته رخ داد و مجبور شدم LocalDb را مجددا نصب نمایم ولی همچنان مشکل وجود داشت. بعد از جستجو به این لینک و این لینک رسیدم که جمع بندی آن بشرح زیر است:
ابتدا command  prompt را اجرا می‌نماییم (ترجیحا بصورت Administrator اجرا شود) سپس به محلی که Instanceهای LocalDb قرار دارد
 (localappdata\Microsoft\Microsoft SQL Server Local DB\Instances\v11.0) رفته و دستور زیر را صادر می‌نماییم:
sqllocaldb create "v11.0"
در صورتیکه خطای زیر نمایش داده شد، مراحل بعد را انجام می‌دهیم.
Creation of LocalDB instance "v11.0" with version 11.0 failed because of the following error: 
LocalDB instance is corrupted. See the Windows Application event log for event details.
هنگامیکه خطای فوق نمایش داده شده، باید ابتدا LocalDb را حذف و مجددا آن را ساخت.
sqllocaldb delete "v11.0"
دستور فوق عملیات حذف را انجام می‌دهد.
و دستور زیر مجددا یک وهله‌ی نسخه 11.0 بنام"v11.0" را از LocalDb ایجاد می‌کند.
sqllocaldb create "v11.0"
تا اینجا کلیه‌ی اقدامات لازم جهت راه اندازی مجدد LocalDb انجام شد و هم اکنون می‌توانید از آن استفاده نمایید.
مطالب
آشنایی با CLR: قسمت نهم
net framework. شامل Framework Class Library یا به اختصار FCL است. FCL مجموعه‌ای از dll اسمبلی‌هایی است که صدها و هزاران نوع در آن تعریف شده‌اند و هر نوع تعدادی کار انجام می‌دهد. همچنین مایکروسافت کتابخانه‌های اضافه‌تری را چون azure و Directx نیز ارائه کرده است که باز هر کدام شامل نوع‌های زیادی می‌شوند. این کتابخانه به طور شگفت آوری باعث سرعت و راحتی توسعه دهندگان در زمینه فناوری‌های مایکروسافت گشته است.

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

Web Service: این فناوری اجازه‌ی ارسال و دریافت پیام‌های تحت شبکه را به خصوص بر روی اینترنت، فراهم می‌کند و باعث ارتباط جامع‌تر بین برنامه‌ها و فناوری‌های مختلف می‌گردد. در انواع جدیدتر WCF و Web Api نیز به بازار ارائه شده‌اند.

webform و MVC : فناوری‌های تحت وب که باعث سهولت در ساخت وب سایت‌ها می‌شوند که وب فرم رفته رفته به سمت منسوخ شدن پیش می‌رود و در صورتی که قصد دارید طراحی وب را آغاز کنید توصیه میکنم از همان اول به سمت MVC بروید.

Rich Windows GUI Application : برای سهولت در ایجاد برنامه‌های تحت وب حالا چه با فناوری WPF یا فناوری قدیمی و البته منسوخ شده Windows Form.
Windows Console Application: برای ایجاد برنامه‌های ساده و بدون رابط گرافیکی.
Windows Services: شما می‌توانید یک یا چند سرویس تحت ویندوز را که توسط Service Control Manager یا به اختصار SCM کنترل می‌شوند، تولید کنید.
Database stored Procedure: نوشتن stored procedure بر روی دیتابیس‌هایی چون sql server و اوراکل و ... توسط فریم ورک دات نت مهیاست.
Component Libraray: ساخت اسمبلی‌های واحدی که می‌توانند با انواع مختلفی از موارد بالا ارتباط برقرار کنند.
Portable Class Libary : این نوع پروژه‌ها شما را قادر می‌سازد تا کلاس‌هایی با قابلیت انتقال پذیری برای استفاده در سیلور لایت، ویندوز فون و ایکس باکس و فروشگاه ویندوز و ... تولید کنید.

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

در CLR مفهومی به نام Common Type System یا CTS وجود دارد که توضیح می‌دهد نوع‌ها باید چگونه تعریف شوند و چگونه باید رفتار کنند که این قوانین از آنجایی که در ریشه‌ی CLR نهفته است، بین تمامی زبان‌های دات نت مشترک می‌باشد. تعدادی از مشخصات این CTS در زیر آورده شده است ولی در آینده بررسی بیشتری روی آنان خواهیم داشت:

  • فیلد
  • متد
  • پراپرتی
  • رویدادها

CTS همچنین شامل قوانین زیادی در مورد وضعیت کپسوله سازی برای اعضای یک نوع دارد:

  • private
  • public
  • Family یا در زبان‌هایی مثل سی ++ و سی شارپ با نام protected شناخته می‌شود.
  • family and assembly: این هم مثل بالایی است ولی کلاس مشتق شده باید در همان اسمبلی باشد. در زبا‌ن‌هایی چون سی شارپ و ویژوال بیسیک، چنین امکانی پیاده سازی نشده‌است و دسترسی به آن ممکن نیست ولی در IL Assembly چنین قابلیتی وجود دارد.
  • Assembly یا در بعضی زبان‌ها به نام internal شناخته می‌شود.
  • Family Or Assembly: که در سی شارپ با نوع Protected internal شناخته می‌شود. در این وضعیت هر عضوی در هر اسمبلی قابل ارث بری است و یک عضو فقط می‌تواند در همان اسمبلی مورد استفاده قرار بگیرد.

موارد دیگری که تحت قوانین CTS هستند مفاهیم ارث بری، متدهای مجازی، عمر اشیاء و .. است.

یکی دیگر از ویژگی‌های CTS این است که همه‌ی نوع‌ها از نوع شیء Object که در فضای نام system قرار دارد ارث بری کرده‌اند. به همین دلیل همه‌ی نوع‌ها حداقل قابلیت‌هایی را که یک نوع object ارئه میدهد، دارند که به شرح زیر هستند:

  • مقایسه‌ی دو شیء از لحاظ برابری.
  • به دست آوردن هش کد برای هر نمونه از یک شیء
  • ارائه‌ای از وضعیت شیء به صورت رشته ای
  • دریافت نوع شیء جاری
CLS
وجود COM‌ها به دلیل ایجاد اشیاء در یک زبان متفاوت بود تا با زبان دیگر ارتباط برقرار کنند. در طرف دیگر CLR هم بین زبان‌های برنامه نویسی یکپارچگی ایجاد کرده است. یکپارچگی زبان‌های برنامه نویسی علل زیادی دارند. اول اینکه رسیدن به هدف یا یک الگوریتم خاص در زبان دیگر راحت‌تر از زبان پایه پروژه است. دوم در یک کار تیمی که افراد مختلف با دانش متفاوتی حضور دارند و ممکن است زیان هر یک متفاوت باشند.
برای ایجاد این یکپارچگی، مایکروسافت سیستم CLS یا Common Language Specification را راه اندازی کرد. این سیستم برای تولیدکنندگان کامپایلرها جزئیاتی را تعریف می‌کند که کامپایلر آن‌ها را باید با حداقل ویژگی‌های تعریف شده‌ی CLR، پشتیبانی کند.


CLR/CTS مجموعه‌ای از ویژگی‌ها را شامل می‌شود و گفتیم که هر زبانی بسیاری از این ویژگی‌ها را پشتیبانی می‌کند ولی نه کامل. به عنوان مثال برنامه نویسی که قصد کرده از IL Assembly استفاده کند، قادر است از تمامی این ویژگی‌هایی که CLR/CTS ارائه می‌دهند، استفاده کند ولی تعدادی دیگر از زبان‌ها مثل سی شارپ و فورترن و ویژوال بیسیک تنها بخشی از آن را استفاده می‌کنند و CLS حداقل ویژگی که بین همه این زبان‌ها مشترک است را ارائه می‌کند.
شکل زیر را نگاه کنید:

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

به کد زیر دقت کنید. تعدادی از کدها سازگاری کامل با CLS دارند که به آن‌ها CLS Compliant گویند و تعدادی از آن‌ها non-CLS-Compliant هستند یعنی با CLS سازگاری ندارند ولی استفاده از خاصیت [(assembly: CLSCompliant(true]  باعث می‌شود که تا کامپایلر از پشتیبانی و سازگاری این کدها اطمینان کسب کند و در صورت وجود، از اجرای آن جلوگیری کند. با کمپایل کد زیر دو اخطار به ما میرسد.
using System;

// Tell compiler to check for CLS compliance
[assembly: CLSCompliant(true)]

namespace SomeLibrary {

// Warnings appear because the class is public
public sealed class SomeLibraryType {

// Warning: Return type of 'SomeLibrary.SomeLibraryType.Abc()'
// is not CLS­compliant
public UInt32 Abc() { return 0; }

// Warning: Identifier 'SomeLibrary.SomeLibraryType.abc()'
// differing only in case is not CLS­compliant
public void abc() { }

// No warning: this method is private
private UInt32 ABC() { return 0; }
}
}

اولین اخطار اینکه یکی از متدها یک عدد صحیح بدون علامت unsigned integer را بر می‌گرداند که همه‌ی زبان‌ها آن را پشتیبانی نمی‌کنند و خاص بعضی از زبان هاست.
دومین اخطار اینکه دو متد یکسان وجود دارند که در حروف بزرگ و کوچک تفاوت دارند. ولی زبان هایی چون ویژوال بیسیک نمی‌توانند تفاوتی بین دو متد abc و ABC بیابند.

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

سازگاری با کدهای مدیریت نشده
در بالا در مورد یکپارچگی و سازگاری کدهای مدیریت شده توسط CLS صحبت کردیم ولی در مورد ارتباط با کدهای مدیریت نشده چطور؟
مایکروسافت موقعیکه CLR را ارئه کرد، متوجه این قضیه بود که بسیاری از شرکت‌ها توانایی اینکه کدهای خودشون را مجددا طراحی و پیاده سازی کنند، ندارند و خوب، سورس‌های مدیریت نشده‌ی زیادی هم موجود هست که توسعه دهندگان علاقه زیادی به استفاده از آن‌ها دارند. در نتیجه مایکروسافت طرحی را ریخت که CLR هر دو قسمت کدهای مدیریت شده و نشده را پشتیبانی کند. دو نمونه از این پشتیبانی را در زیر بیان می‌کنیم:
یک. کدهای مدیریت شده می‌توانند توابع مدیریت شده را در قالب یک dll صدا زده و از آن‌ها استفاده کنند.
دو. کدهای مدیریت شده می‌توانند از کامپوننت‌های COM استفاده کنند: بسیاری از شرکت‌ها از قبل بسیاری از کامپوننت‌های COM را ایجاد کرده بودند که کدهای مدیریت شده با راحتی با آن‌ها ارتباط برقرار می‌کنند. ولی اگر دوست دارید روی آن‌ها کنترل بیشتری داشته باشید و آن کدها را به معادل CLR تبدیل کنید؛ میتوانید از ابزار کمکی که مایکروسافت همراه فریم ورک دات نت ارائه کرده است استفاده کنید. نام این ابزار TLBIMP.exe می‌باشد که از Type Library Importer گرفته شده است.

سه. اگر کدهای مدیریت نشده‌ی زیادتری دارید شاید راحت‌تر باشد که برعکس کار کنید و کدهای مدیریت شده را در در یک برنامه‌ی مدیریت نشده اجرا کنید. این کدها می‌توانند برای مثال به یک Activex یا shell Extension تبدیل شده و مورد استفاده قرار گیرند. ابزارهای TLBEXP .exe و RegAsm .exe برای این منظور به همراه فریم ورک دات نت عرضه شده اند.
سورس کد Type Library Importer را میتوانید در کدپلکس بیابید.
در ویندوز 8 به بعد مایکروسافت API جدید را تحت عنوان WinsowsRuntime یا winRT ارائه کرده است . این api یک سیستم داخلی را  از طریق کامپوننت‌های com ایجاد کرده و به جای استفاده از فایل‌های کتابخانه‌ای، کامپوننت‌ها api هایشان را از طریق متادیتاهایی بر اساس استاندارد ECMA که توسط تیم دات نت طراحی شده است معرفی می‌کنند.
زیبایی این روش اینست که کد نوشته شده در زبان‌های دات نت  می‌تواند به طور مداوم با api‌های winrt ارتباط برقرار کند. یعنی همه‌ی کارها توسط CLR انجام می‌گیرد بدون اینکه لازم باشد از ابزار اضافی استفاده کنید. در آینده در مورد winRT بیشتر صحبت می‌کنیم.
 
سخن پایانی: ممنون از دوستان عزیز بابت پیگیری مطالب تا بدینجا. تا این قسمت فصل اول کتاب با عنوان اصول اولیه CLR بخش اول مدل اجرای CLR به پایان رسید.
ادامه‌ی مطالب بعد از تکمیل هر بخش در دسترس دوستان قرار خواهد گرفت.
مطالب
ارسال پارامتر از سی شارپ به مایکروسافت Word
فرض کنید نامه‌ای را می‌خواهیم تنظیم کنیم. سمت برنامه، شماره، تاریخ و نام مدیر عامل و ... را مشخص می‌کنیم و می‌خواهیم این اطلاعات را به ورد بفرستیم؛ همچنین متن نامه را هم در ورد تایپ کنیم و در آخر هم نامه را آرشیو کنیم. برای اینکار چندین روش وجود دارد. ما در این مقاله از روش MailMergeField و Bookmark استفاده میکنیم.

روش ایجاد الگوهای Word

ابتدا می‌خواهیم یک الگو یا Template را درست کنیم و بعد‌ها از روی آن، نامه‌ی جدیدی را ایجاد کنیم و فیلدهایش را پرکنیم. برای اینکار یک سند جدید را در Word ایجاد و به سربرگ Mailings مراجعه میکنیم. سپس دکمه‌ی Select Recipients را بزنید. در ادامه از منوی باز شده، Type a NewList را بزنید. با اینکار پنجره‌ای باز می‌شود. در اینجا دکمه‌ی Customize Columns را بزنید. این پنجره شامل فیلدهایی می‌شود که میتوانید از آن استفاده کنید و بر روی سند قرار دهید و داخل برنامه با پیدا کردن این فیلدها میتوانید بجای آن‌ها، مقدار مورد نظرتان را پاس دهید. حالا شما نیاز دارید تا از طریق دکمه‌ی Add، تمامی فیلدهای لازم یک نامه را بسازید. پس از این کار، در هر دو پنجره ، دکمه‌ی OK را بزنید. بدین صورت یک پنجره‌ی ذخیره برای شما باز می‌شود تا این فیلدهایی  را که ایجاد کردید، به عنوان یک دیتابیس کوچک ذخیره شود که تمامی فیلدها را دارا می‌باشد و هر موقع که خواستید دوباره میتوانید از همین فیلد‌ها استفاده کنید.

حالا می‌رسیم به قرار دادن این فیلد‌ها داخل سند. با ذخیره کردن فیلدها، تمامی گزینه‌های سربرگ Mailings فعال می‌شود. شما برای اینکه فیلدی را بر روی سند قرار دهید، روی Insert Merge Field کلیک و متناسب با نیازتان، فیلدها را قرار دهید و الگو را طراحی کنید. یک نمونه:


حالا فایل را با پسوند DOT. ذخیره کنید. در ادامه این فایل را در دیتابیس، به این روش ذخیره کنید: 

String FilePath = "Template Path"
// Converting File to ByteArray
byte[] FileBuffer = System.IO.File.ReadAllBytes(FilePath);
// Now you can insert this file buffer to DB

الان، الگوی ما آماده‌است و میتوانیم از طریق برنامه، به این الگو دسترسی داشته باشیم و به آن پارامتر ارسال کنیم.


روش ارسال پارامترها به الگوهای Word

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

بنابراین در ادامه ابتدا Assembly مربوط به MicroSoft.Office.Interop.Word را به رفرنس‌های پروژه اضافه میکنیم و سربرگش را هم Using میکنیم.


حالا می‌رسیم به کد نویسی:

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

//LOCATION OF THE TEMPLATE FILE ON THE MACHINE;
Object oTemplatePath = string.Format("{0}\\NewDocument.dot", Application.StartupPath);
 
//OBJECT OF MISSING "NULL VALUE"
Object oMissing = System.Reflection.Missing.Value;
 
//OBJECTS OF FALSE AND TRUE
Object oTrue = true;
Object oFalse = false;
 
//CREATING OBJECTS OF WORD AND DOCUMENT
Microsoft.Office.Interop.Word.Application oWord = null;
Microsoft.Office.Interop.Word.Document oWordDoc = null;

سپس کدهای زیر را داخل رخ‌داد گردان کلیک دکمه‌ی مثلا "پیشنمایش" مینویسیم:
// Fetching Template ByteArray From Database => Byte[] YourTemplateByteArray = Fetch Template;

System.IO.File.WriteAllBytes(oTemplatePath.ToString(), YourByteArray);

oWord = new Microsoft.Office.Interop.Word.Application();
oWordDoc = new Microsoft.Office.Interop.Word.Document();

//Adding A New Document From A Template
oWordDoc = oWord.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);

int iTotalFields = 0;
// Finding Mailmerge Fields
foreach(Microsoft.Office.Interop.Word.Field myMergeField in oWordDoc.Fields) {
  iTotalFields++;
  Microsoft.Office.Interop.Word.Range rngFieldCode = myMergeField.Code;
  String fieldText = rngFieldCode.Text;

  // Only Get The Mailmerge Fields
  if (fieldText.StartsWith(" MERGEFIELD")) {
    // Gives The Fieldnames as Entered in .DOT File
    string fieldName = fieldText.Substring(12, fieldText.IndexOf(" ", 12) - 12);

    switch (fieldName) {
    case "Letter_No":
      myMergeField.Select();
      oWord.Selection.TypeText(txtLetterNo.Text);
      break;

    case "Letter_Date":
      myMergeField.Select();
      oWord.Selection.TypeText(DateTime.Now);
      break;

    case "Letter_Has_Attachment":
      myMergeField.Select();
      oWord.Selection.TypeText("دارد یا ندارد");
      break;

      // And So On
    default:
      break;
    }
  }
}

//Showing The Document To The User
oWord.Visible = true;

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

oWordDoc.Save();

//Closing the file
oWordDoc.Close(ref oFalse, ref oMissing, ref oMissing);

//Quitting the application
oWord.Quit(ref oMissing, ref oMissing, ref oMissing);
byte[] FileBuffer = System.IO.File.ReadAllBytes(oTemplatePath.ToString  ());
 
// Now Insert The FileBuffer Into Database as A Letter


خوب؛ کار تمام است! حالا فیلد FileBuffer را باید بسته به کدنویسی خودتان، داخل دیتابیس ذخیره کنید که برای بعدها بتوانید آن‌را واکشی کرده و به کاربر نمایش دهید. این هم نمونه‌ی نهایی جایگذاری فیلدها: 


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