نظرات مطالب
دریافت کتاب از Google books
سلام
جالب بود مواجه شدن با محدودیت، برای هیچ کس جالب نیست.

ضمنا قابلیت انتخاب فرمت نمایش تاریخ به ShamsiXp 0.25 اضافه شده و سورس کد آن نیز پیوست شده است. (http://mohammadshams.blogspot.com/2009/09/shamsi-xp-025-source-code.html)

برای ادامه این کار به شکل صحیح و اضافه کردن Calendar Type فارسی به استانداردهای ویندوز، تاپیکی در بخش امنیت نرم‌افزار برنامه‌نویس اضافه شد.
امیدوارم با همکاری دیگر دوستان بتوان این "محدودیت" را هم از بین برد.
با تشکر
مسیرراه‌ها
ASP.NET MVC
              مطالب
              شروع به کار با AngularJS 2.0 و TypeScript - قسمت چهارم - data binding
              در قسمت قبل، نگاهی مقدماتی داشتیم به مبحث data binding. در ادامه، این مبحث را به همراه pipes، جهت اعمال تغییرات بر روی اطلاعات، پیگیری خواهیم کرد.


              انقیاد به خواص یا property binding

              قابلیت property binding این امکان را فراهم می‌کند که یکی از خواص المان‌های HTML را به مقادیر دریافتی از کلاس کامپوننت، متصل کنیم:
               <img [src]='producr.imageUrl'>
              در این مثال، خاصیت src المان تصویر، به آدرس تصویر یک محصول متصل شده‌است.
              در حین تعریف property binding، مقصد اتصال، داخل براکت‌ها قرار می‌گیرد و خاصیت مدنظر المان را مشخص می‌کند. منبع اتصال همیشه داخل "" در سمت راست علامت مساوی قرار می‌گیرد.
              اگر اینکار را بخواهیم با interpolation معرفی شده‌ی در قسمت قبل انجام دهیم، به کد ذیل خواهیم رسید:
               <img src={{producr.imageUrl}}>
              در اینجا نه از []، برای معرفی مقصد اتصال استفاده شده‌است و نه از "" برای مشخص سازی منبع اتصال. این نوع اتصال یک طرفه است (از منبع به مقصد).

              خوب، در یک چنین مواردی property binding بهتر است یا interpolation؟
              توصیه‌ی کلی ترجیح property binding به interpolation است. اما اگر در اینجا نیاز به انجام محاسباتی بر روی عبارت منبع وجود داشت، باید از interpolation استفاده کرد؛ مانند:
               <img src='http://www.mysite.com/images/{{producr.imageUrl}}'>


              تکمیل قالب کامپوننت لیست محصولات

              اگر از قسمت قبل به خاطر داشته باشید، در فایل product-list.component.html، لیست پردازش شده‌ی توسط ngFor*، فاقد ستون نمایش تصاویر محصولات است. به همین جهت فایل یاد شده را گشوده و سپس با استفاده از property binding، دو خاصیت src و title تصویر را به منبع داده‌ی آن متصل می‌کنیم:
              <tbody>
                  <tr *ngFor='#product of products'>
                      <td>
                          <img [src]='product.imageUrl'
                               [title]='product.productName'>
                      </td>
                      <td>{{ product.productName }}</td>
                      <td>{{ product.productCode }}</td>
                      <td>{{ product.releaseDate }}</td>
                      <td>{{ product.price }}</td>
                      <td>{{ product.starRating }}</td>
                  </tr>
              </tbody>
              در این حالت اگر برنامه را اجرا کنیم به خروجی ذیل خواهیم رسید:


              هرچند اینبار تصاویر محصولات نمایش داده شده‌اند، اما اندکی بزرگ هستند. بنابراین در ادامه با استفاده از property binding، خواص style آن‌را تنظیم خواهیم کرد. برای این منظور فایل product-list.component.ts را گشوده و به کلاس ProductListComponent، دو خاصیت imageWidth و imageMargin را اضافه می‌کنیم:
              export class ProductListComponent {
                  pageTitle: string = 'Product List';
                  imageWidth: number = 50;
                  imageMargin: number = 2;
                  products: any[] = [
                      // as before...
                  ];
              }
              البته همانطور که پیشتر نیز ذکر شد، چون مقادیر پیش فرض این خواص عددی هستند، نیازی به ذکر صریح نوع number در اینجا وجود ندارد (type inference).
              پس از تعریف این خواص، امکان دسترسی به آن‌ها در قالب کامپوننت وجود خواهد داشت:
              <tbody>
                  <tr *ngFor='#product of products'>
                      <td>
                          <img [src]='product.imageUrl'
                               [title]='product.productName'
                               [style.width.px]='imageWidth'
                               [style.margin.px]='imageMargin'>
                      </td>
              همانطور که مشاهده می‌کنید، چون خاصیت‌های جدید تعریف شده، جزئی از خواص اصلی کلاس هستند و نه خواص اشیاء لیست محصولات، دیگر به همراه .product ذکر نشده‌اند.
              همچنین در اینجا نحوه‌ی style binding را نیز مشاهده می‌کنید. مقصد اتصال همیشه با [] مشخص می‌شود و سپس کار با ذکر .style شروع شده و پس از آن نام خاصیت مدنظر عنوان خواهد شد. اگر نیاز به ذکر واحدی وجود داشت، پس از درج نام خاصیت، قید خواهد شد. برای مثال [style.fontSize.em] و یا [%.style.fontSize]


              یک نکته:
              اگر مثال را قدم به قدم دنبال کرده باشید، با افزودن style binding و بارگذاری مجدد صفحه، احتمالا تغییراتی را مشاهده نخواهید کرد. این مورد به علت کش شدن قالب قبلی و یا فایل جاوا اسکریپتی متناظر با آن است (فایلی که خواص عرض و حاشیه‌ی تصویر به آن اضافه شده‌اند).
              یک روش ساده‌ی حذف کش آن، بازکردن آدرس http://localhost:2222/app/products/product-list.component.js در مرورگر به صورت مجزا و سپس فشردن دکمه‌های ctrl+f5 بر روی آن است.


              پاسخ دادن به رخدادها و یا event binding

              تا اینجا تمام data binding‌های تعریف شده‌ی ما یک طرفه بودند؛ از خواص کلاس کامپوننت به اجزای قالب متناظر با آن. اما گاهی از اوقات نیاز است تا با کلیک کاربر بر روی دکمه‌ای، عملی خاص صورت گیرد و در این حالت، جهت ارسال اطلاعات، از قالب کامپوننت، به متدها و خواص کلاس متناظر با آن خواهند بود. کامپوننت به اعمال کاربر از طریق event binding گوش فرا می‌دهد:
               <button (click)='toggleImage()'>
              syntax آن بسیار شبیه است به حالت property binding و در اینجا بجای [] از () جهت مشخص سازی رخدادی خاص از المان مدنظر استفاده می‌شود. سمت راست این انتساب، متدی است که داخل "" قرار می‌گیرد و این متد متناظر است با متدی مشخص در کلاس متناظر با کامپوننت جاری.
              در این حالت اگر کاربر روی دکمه‌ی تعریف شده کلیک کند، متد toggleImage موجود در کلاس متناظر، فراخوانی خواهد شد.
              چه رخدادهایی را در اینجا می‌توان ذکر کرد؟ پاسخ آن‌را در آدرس ذیل می‌توانید مشاهده کنید:
              https://developer.mozilla.org/en-US/docs/Web/Events

              این syntax جدید AngularJS 2.0 سطح API آن‌را کاهش داده است. دیگر در اینجا نیازی نیست تا به ازای هر رخداد ویژه‌ای، یک دایرکتیو و یا syntax خاص آن‌را در مستندات آن
              جستجو کرد. فقط کافی است syntax جدید (نام رخداد) را مدنظر داشته باشید.


              تکمیل مثال نمایش لیست محصولات با فعال سازی دکمه‌ی Show Image آن

              در اینجا قصد داریم با کلیک بر روی دکمه‌ی Show image، تصاویر موجود در ستون تصاویر، مخفی و یا نمایان شوند. برای این منظور خاصیت جدیدی را به نام showImage به کلاس ProductListComponent اضافه می‌کنیم. بنابراین فایل product-list.component.ts را گشوده و تغییر ذیل را به آن اعمال کنید:
              export class ProductListComponent {
                  pageTitle: string = 'Product List';
                  imageWidth: number = 50;
                  imageMargin: number = 2;
                  showImage: boolean = false;
              در اینجا خاصیت Boolean جدیدی به نام showImage با مقدار اولیه‌ی false تعریف شده‌است. به این ترتیب تصاویر، در زمان اولین بارگذاری صفحه نمایش داده نخواهند شد.
              سپس به انتهای کلاس، پس از تعاریف خواص، متد جدید toggleImage را اضافه می‌کنیم:
              export class ProductListComponent {
                  // as before ...
               
                  toggleImage(): void {
                      this.showImage = !this.showImage;
                  }
              }
              در کلاس‌های TypeScript نیازی به ذکر صریح واژه‌ی کلیدی function برای تعریف متدی وجود ندارد. این متد، خروجی هم ندارد، بنابراین نوع خروجی آن void مشخص شده‌است. در بدنه‌ی این متد، وضعیت خاصیت نمایش تصویر معکوس می‌شود.
              پس از این تغییرات، اکنون می‌توان به قالب این کامپوننت یا فایل product-list.component.html مراجعه و event binding را تنظیم کرد:
              <button class='btn btn-primary'
                      (click)='toggleImage()'>
                  Show Image
              </button>
              در اینجا click به عنوان رخداد مقصد، مشخص شده‌است. سپس آن‌را به متد toggleImage کلاس ProductListComponent متصل می‌کنیم.
              خوب، تا اینجا اگر کاربر بر روی دکمه‌ی show image کلیک کند، مقدار خاصیت showImage کلاس ProductListComponent با توجه به کدهای متد toggleImage، معکوس خواهد شد.
              مرحله‌ی بعد، استفاده از مقدار این خاصیت، جهت مخفی و یا نمایان ساختن المان تصویر جدول نمایش داده شده‌است. اگر از قسمت قبل به خاطر داشته باشید، کار ngIf*، حذف و یا افزودن المان‌های DOM است. بنابراین ngIf* را به المان تصویر جدول اضافه می‌کنیم:
              <tr *ngFor='#product of products'>
                  <td>
                      <img *ngIf='showImage'
                           [src]='product.imageUrl'
                           [title]='product.productName'
                           [style.width.px]='imageWidth'
                           [style.margin.px]='imageMargin'>
                  </td>
              با توجه به ngIf* تعریف شده، المان تصویر تنها زمانی به DOM اضافه خواهد شد که مقدار خاصیت showImage مساوی true باشد.

              اکنون برنامه را اجرا کنید. در اولین بار اجرای صفحه، تصاویر ستون اول جدول، نمایش داده نمی‌شود. پس از کلیک بر روی دکمه‌ی Show image، این تصاویر نمایان شده و اگر بار دیگر بر روی این دکمه کلیک شود، این تصاویر مخفی خواهند شد.

              یک مشکل! در هر دو حالت نمایش و مخفی سازی تصاویر، برچسب این دکمه Show image است. بهتر است زمانیکه قرار است تصاویر مخفی شوند، برچسب hide image نمایش داده شود و برعکس. برای حل این مساله از interpolation به نحو ذیل استفاده خواهیم کرد:
              <button class='btn btn-primary'
                      (click)='toggleImage()'>
                  {{showImage ? 'Hide' : 'Show'}} Image
              </button>
              در اینجا اگر مقدار خاصیت showImage مساوی true باشد، مقدار رشته‌ای Hide و اگر false باشد، مقدار رشته‌ای show بجای {{}} درج خواهد شد.



              بررسی انقیاد دو طرفه یا two-way binding

              تا اینجا، اتصال مقدار یک خاصیت عمومی کلاس متناظر با قالبی، به اجزای مختلف آن، یک طرفه بودند. اما در ادامه نیاز است تا بتوان برای مثال در textbox قسمت filter by مثال جاری بتوان اطلاعاتی را وارد کرد و سپس بر اساس آن ردیف‌های جدول نمایش داده شده را فیلتر نمود. این عملیات نیاز به انقیاد دو طرفه یا two-way data binding دارد.
              برای تعریف انقیاد دو طرفه در AngularJS 2.0 از دایرکتیو توکاری به نام ngModel استفاده می‌شود:
               <input [(ngModel)]='listFilter' >
              ابتدا [] ذکر می‌شود تا مشخص شود که این عملیات در اصل یک property binding است؛ از خاصیت عمومی به نام listFilter به المان textbox تعریف شده.
              سپس () تعریف شده‌است تا event binding را نیز گوشزد کند. کار آن انتقال تعاملات کاربر، با المان رابط کاربری جاری، به خاصیت عمومی کلاس یا همان listFilter است.

              در اینجا ممکن است که فراموش کنید [()] صحیح است یا ([]) . به همین جهت به این syntax، نام banana in the box را داده‌اند یا «موز درون جعبه»! موز همان event binding است که داخل جعبه‌ی property binding قرار می‌گیرد!

              خوب، برای اعمال انقیاد دو طرفه، به مثال جاری، فایل product-list.component.ts را گشوده و خاصیت رشته‌ای listFilter را به آن اضافه می‌کنیم:
              export class ProductListComponent {
                  pageTitle: string = 'Product List';
                  imageWidth: number = 50;
                  imageMargin: number = 2;
                  showImage: boolean = false;
                  listFilter: string = 'cart';
              سپس فایل قالب product-list.component.html را گشوده و انقیاد دو طرفه را به آن اعمال می‌کنیم:
              <div class='panel-body'>
                  <div class='row'>
                      <div class='col-md-2'>Filter by:</div>
                      <div class='col-md-4'>
                          <input type='text'
                                 [(ngModel)]='listFilter' />
                      </div>
                  </div>
                  <div class='row'>
                      <div class='col-md-6'>
                          <h3>Filtered by: {{listFilter}}</h3>
                      </div>
                  </div>
              در اینجا انقیاد دو طرفه، توسط ngModel، به خاصیت listFilter کلاس، در المان input تعریف شده، صورت گرفته است‌. سپس توسط interpolation مقدار این تغییر را در قسمت Filtered by به صورت یک برچسب نمایش می‌دهیم.


              پس از اجرای برنامه، تکست باکس تعریف شده، مقدار اولیه‌ی cart را خواهد داشت و اگر آن‌را تغییر دهیم، بلافاصله این مقدار تغییر یافته را در برچسب Filtered by می‌توان مشاهده کرد. به این رخداد two-way binding می‌گویند.
              البته هنوز کار فیلتر لیست محصولات در اینجا انجام نمی‌شود که آن‌را در قسمت بعد تکمیل خواهیم کرد.


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

              تا اینجا لیست محصولات نمایش داده شد، اما نیاز است برای مثال فرمت ستون نمایش قیمت آن بهبود یابد. برای این منظور، از ویژگی دیگری به نام pipes استفاده می‌شود و کار آن‌ها تغییر داده‌ها، پیش از نمایش آن‌ها است. AngularJS 2.0 به همراه تعدادی pipe توکار برای فرمت مقادیر است؛ مانند date، number، decimal، percent و غیره. همچنین امکان ساخت custom pipes نیز پیش بینی شده‌است.
              در اینجا یک مثال ساده‌ی pipes را مشاهده می‌کنید:
               {{ product.productCode | lowercase }}
              پس از قید نام خاصیتی که قرار است نمایش داده شود، حرف pipe یا | قرار گرفته و سپس نوع pipe ذکر می‌شود. به این ترتیب کد محصول، پیش از نمایش، ابتدا به حروف کوچک تبدیل شده و سپس نمایش داده می‌شود.

              از pipes در property binding هم می‌توان استفاده کرد:
               [title]='product.productName | uppercase'
              در اینجا برای مثال عنوان تصویر با حروف بزرگ نمایش داده خواهد شد.

              و یا می‌توان pipes را به صورت زنجیره‌ای نیز تعریف کرد:
               {{ product.price | currency | lowercase }}
              در اینجا pipe توکار currency سبب نمایش سه حرف اول واحد پولی، با حروف بزرگ می‌شود. اگر علاقمند بودیم که آن‌را با حروف کوچک نمایش دهیم می‌توان یک pipe دیگر را در انتهای این زنجیره قید کرد.

              بعضی از pipes، پارامتر هم قبول می‌کنند:
               {{ product.price | currency:'USD':true:'1.2-2' }}
              در اینجا هر پارامتر با یک : مشخص می‌شود. برای مثال pipe واحد پولی، سه پارامتر را دریافت می‌کند: یک کد دلخواه، نمایش یا عدم نمایش علامت پولی، بجای کد دلخواه و مشخصات ارقام نمایش داده شده. برای مثال 2-1.2، یعنی حداقل یک عدد پیش از اعشار، حداقل دو عدد پس از اعشار و حداکثر دو عدد پس از اعشار باید ذکر شوند (یعنی در نهایت دو رقم اعشار مجاز است).
              مبحث ایجاد custom pipes را در قسمت بعدی دنبال خواهیم کرد.

              در ادامه برای ویرایش مثال جاری، فایل قالب product-list.component.html را گشوده و سطرهای جدول را به نحو ذیل تغییر دهید:
              <td>{{ product.productName }}</td>
              <td>{{ product.productCode | lowercase }}</td>
              <td>{{ product.releaseDate }}</td>
              <td>{{ product.price | currency:'USD':true:'1.2-2'}}</td>
              <td>{{ product.starRating }}</td>
              در اینجا با استفاده از pipes، شماره محصول با حروف کوچک و قیمت آن تا حداکثر دو رقم اعشار، فرمت خواهند شد.
              اینبار اگر برنامه را اجرا کنید، یک چنین خروجی را مشاهده خواهید کرد:


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


              خلاصه‌ی بحث

              data binding سبب سهولت نمایش مقادیر خواص کلاس یک کامپوننت، در قالب آن می‌شود. در AngularJS 2.0، چهار نوع binding وجود دارند:


              interpolation، عبارت رشته‌ای محاسبه شده را در بین المان‌های DOM درج می‌کند و یا می‌تواند خاصیت المانی را مقدار دهی نماید.
              property binding سبب اتصال مقدار خاصیتی، به یکی از خواص المانی مشخص در DOM می‌شود.
              event binding به رخ‌دادها گوش فرا داده و سبب اجرای متدی در کلاس کامپوننت، در صورت بروز رخداد متناظری می‌شود.
              حالت two-way binding، کار دریافت اطلاعات از کلاس و همچنین بازگشت مقادیر تغییر یافته‌ی توسط کاربر را به کلاس انجام می‌دهد.
              اطلاعات نمایش داده شده‌ی توسط binding عموما فرمت مناسبی را ندارد. برای رفع این مشکل از pipes استفاده می‌شود.
              مطالب
              NHibernate 3.0 و عدم وابستگی مستقیم به Log4Net

              اولین موردی که پس از دریافت NHibernate 3.0 ممکن است به چشم بخورد، نبود اسمبلی Log4Net است. مطابق درخواست‌های کاربران، ارجاع مستقیم به این کتابخانه حذف شده و با یک اینترفیس عمومی به نام IInternalLogger جایگزین گشته است (قرار گرفته در فضای نام NHibernate.Logging). به این صورت می‌توان از انواع و اقسام کتابخانه‌های ثبت وقایع نوشته شده برای دات نت استفاده کرد؛ مانند: log4net، Nlog، EntLib Logging و غیره.
              البته لازم به ذکر است که همان روش قبلی استفاده از Log4Net هنوز هم پشتیبانی می‌شود (بدون نیاز به تغییر خاصی در کدهای خود)، زیرا پیاده سازی اینترفیس جدید IInternalLogger برای استفاده از آن به صورت پیش فرض توسط NHibernate انجام شده است.

              یک مثال:

              کتابخانه‌ی سورس باز Common.Logging واقع شده در سورس فورج، در واقع یک logging abstraction framework است. به این معنا که تا به حال کتابخانه‌‌های ثبت وقایع مختلف و متعددی برای دات نت فریم ورک نوشته شده است و هر کدام راه و روش و پیاده سازی خاص خود را دارند. کتابخانه‌ی Common.Logging لایه‌ای است عمومی بر روی تمام این کتابخانه‌ها مانند Log4Net، Enterprise Library Logging ، Nlog و غیره که برنامه‌ی شما را از وابستگی مستقیم به هر کدام از موارد ذکر شده رها می‌سازد.
              اکنون با توجه به وجود اینترفیس IInternalLogger در NHibernate 3.0 ، تنها کافی است این اینترفیس جهت استفاده از کتابخانه‌ی Common.Logging پیاده سازی شود (abstraction اندر abstraction !). البته نیازی نیست اینکار انجام شود، زیرا پیشتر توسط پروژه‌ی NHibernate.Logging در سایت کدپلکس اینکار صورت گرفته است.
              بنابراین تنها کاری که باید انجام داد این است که :
              الف) ارجاعاتی را به اسمبلی‌های Common.Logging.dll (واقع در سورس فورج) و NHibernate.Logging.CommonLogging.dll (واقع در کدپلکس) به پروژه‌ی خود اضافه کنید.
              ب) ارجاعی را نیز به اسمبلی کتابخانه‌ی ثبت وقایع مورد نظر خود نیز باید اضافه نمائید (مثلا NLog).
              ج) سپس باید چند سطر زیر را به فایل app.config خود اضافه کنید:

              <appSettings>
              <add key="nhibernate-logger"
              value="NHibernate.Logging.CommonLogging.CommonLoggingLoggerFactory, Hibernate.Logging.CommonLogging"/>
              </appSettings>

              NHibernate.Logging.CommonLogging.dll وقایع داخلی NHibernate را با پیاده سازی IInternalLogger به Common.Logging.dll منتقل می‌کند. سپس Common.Logging.dll این وقایع را به زبان کتابخانه‌ی ثبت وقایع مورد نظر ترجمه خواهد کرد.

              اطلاعات بیشتر: (+)

              مطالب
              نحوه استفاده از Text template ها در دات نت - قسمت چهارم
              در قسمت‌های قبلی (^ و ^ و ^) با Text Template در Visual Studio آشنا شدید. این قسمت برای تکمیل بحث در مورد ابزاری که Microsoft از آن در برنامه‌های خود از جمله Visual Studio جهت تولید کدهای اتوماتیک استفاده می‌نماید، صحبت خواهیم کرد.
              قبل از آن بد نیست که بدانید چرا این ابزار T4  نام گرفته !
              T4 مخفف  Text Template Transformation Toolkit می‌باشد (TTTT). شکل زیر مراحل اجرای یک کد Text Template را توسط T4 نشان میدهد: 


              پس این ابراز، یک ابزار کاربردی می‌باشد که بدون Visual Studio نیز میتوان از آن استفاده کرد. نام فایل این ابزار، TextTransform.exe است و در مسیر زیر وجود دارد :

              Program Files (x86)\Common Files\microsoft shared\TextTemplating\10.0
              برای اطلاع از نحوه کار با TextTransform.exe خارج از محیط Visual Studio بهتر است دستور زیر را در cmd.exe اجرا کنید تا راهنمای استفاده و پارامتر‌های اختیاری آن را مشاهده نمایید:

                TextTransform.exe –h 

              برای آزمایش، یک فایل متنی کنار فایل TextTransform.exe با نام Text2.tt ایجاد نمایید و کد زیر را در داخل آن بنویسید:
              <#@ template debug="true" hostspecific="false" language="C#" #>
              <#@ output extension=".txt"  #>
              
              <#@ import namespace="System.Diagnostics" #>
              
              Report In : <#= DateTime.Now #>
              
              <#
              
              Process[] Procs = Process.GetProcesses();
                  for (int i = 0; i < Procs.Length; i++)
                  {
                      string Pstr = Procs[i].ProcessName + "  -|-  " + Procs[i].Id + Environment.NewLine ;
               #><#= Pstr #><#
                  }
              
              #>
               این مثال بعد از اجرا، لیست تمام Process های جاری سیستم را به همراه Id  آن‌ها، چاپ می‌نماید.
              برای تولید فایل خروجی، دستور زیر را در cmd.exe اجرا کنید :
              TextTransform.exe  -out  Report1.txt  Text2.tt
              توجه کنید که فایل Text2.tt را کنار فایل TextTransform.exe قرار دهید و بعد از اجرای دستور بالا، باید خروجی در فایل Text2.tt در همان مسیر ایجاد گردد.

              نکته: اگر User شما به این پوشه دسترسی ندارد و کاربر Admin نیستید احتمالا به مشکل بر می‌خورد. می‌توانید فایل TextTransform.exe را در مکان دیگری قرار دهید و دستور را از آن محل اجرا کنید و یا برای پوشه‌ی مذکور دسترسی ایجاد نمایید.

              اگر میخواهید بیشتر در مورد معماری T4  بدانید بهتر است مقاله زیر را مطالعه کنید:  

              http://www.olegsych.com/2008/05/t4-architecture/

              نکته دیگر این که برای Visual Studio، ابزارهایی جهت بهبود کار با Text Template‌ها وجود دارند که با جستجوی T4 Editor، نمونه‌هایی از آنها را خواهید یافت. tangible T4 Editor نمونه ای از این Pluginها می‌باشد که به Visual Studio  افزوده می‌گردد و یا یک پروژه Open Source  هم برای آشنایی بسیار بیشتر با T4 در t4toolbox.codeplex.com وجود دارد که میتوانید مشاهده کنید.
              مطالب
              جزئیات برنامه نویسی افزونه فارسی به پارسی

              این افزونه با استفاده از ابزار Visual Studio Tools for Office که به VSTO مشهور شده است، تهیه شد. در بسته به روز رسانی سیستم که در ذیل (معرفی افزونه) نیز معرفی شد نگارش sp1 vsto3.0 آن به صورت خودکار نصب خواهد شد.
              برای ایجاد این پروژه در VS.Net 2008 ، تنها کافی است یک پروژه جدید Word add-in را آغاز نمائیم. (شکل زیر)





              قبل از ادامه بحث، بهتر است در مورد بانک اطلاعاتی مورد استفاده نیز توضیح داده شود. در اینجا از SQLite استفاده شد. (بسیار سبک، کم حجم و سریع است و اساسا یک کاربر نهایی برای تنظیمات آن نیازی نیست اطلاعاتی داشته باشد). بسته به روز رسانی سیستم (در مطلب قبلی)، این مورد را نیز به صورت خودکار نصب خواهد کرد (در GAC باید نصب شود وگرنه افزونه قادر به یافتن آن نخواهد شد).
              برای ایجاد این بانک اطلاعاتی، از افزونه SQLite manager برای فایرفاکس استفاده شد. (این افزونه رایگان شما را از هر ابزار جانبی برای مدیریت یک بانک اطلاعاتی SQLite بی‌نیاز می‌کند)
              برای مثال فایل ErrorsBank.sqlite برنامه افزونه فارسی به پارسی را توسط افزونه SQLite manager فایرفاکس باز کنید (این فایل را در محل نصب افزونه می‌توانید پیدا کنید). در اینجا می‌توان جداول جدید را ایجاد کرد، کوئری‌های دلخواه را اجرا نمود و یا اطلاعات را مرور کرده، حذف یا ویرایش کرد (شکل زیر).




              و خوشبختانه این بانک اطلاعاتی و محصور کننده‌های آن با اطلاعات یونیکد فارسی هیچ مشکلی ندارند و برای کارهایی با وسعت کم و تعداد رکورد پائین یکی از بهترین انتخاب‌ها به‌شمار می‌روند.
              نحوه استفاده از SQLite نیز در دات نت بسیار ساده است. اگر با ADO.Net کار کرده باشید، پس از افزودن ارجاعی از اسمبلی System.Data.SQLite.DLL به پروژه و معرفی فضای نام آن به پروژه، تنها کافی است در کدهای قبلی خود برای مثال SqlConnection را به SQLiteConnectionتغییر دهید و امثال آن. یعنی دانش ADO.Net شما در اینجا نیز کاملا قابل استفاده خواهد بود و نیازی نیست مدتی را صرف آشنا شدن با کلاس‌ها و مفاهیم جدید نمائید (البته این تنها زمانی معنا خواهد داشت که به ویزاردها عادت نکرده باشید و کارهای خود را با کد نویسی انجام داده باشید).
              تنها یک نکته را باید به‌خاطر داشت و آن هم مربوط است به ساز و کار درونی SQLite . هنگام انجام عملیات update یا insert حتما از transaction استفاده کنید تا سرعت کوئری‌های شما در SQLite به نحو شگفت انگیزی افزایش یابد. مثالی در این مورد را در فایل chm راهنمای SQLite.NET می‌توانید پیدا کنید.

              مطلب دیگری که پیش از پرداختن به کد نویسی افزونه باید با آن آشنا شویم، مفهوم smart tags در مجموعه آفیس است که در این پروژه از آن استفاده گردید.
              smart tags در مجموعه آفیس برچسب‌هایی هستند که به صورت خودکار توسط یکی از محصولات آفیس مثلا ورد یا اکسل و امثال آن، پس از تشخیص یک کلمه خاص ایجاد می‌شوند و می‌توان اعمالی را به این برچسب ایجاد شده انتساب داد. برای مثال در اینجا امکان جایگزین کردن کلمه فارسی با معادل پارسی در نظر گرفته شد.
              ویدیویی در مورد نحوه ایجاد اسمارت تگ‌ها در VS.Net و یا مثالی پیشرفته‌تر در مورد تشخیص دمای فارنهایت در یک متن و ایجاد smart tag مخصوص به آن برای تبدیل به سلسیوس. (از regular expressions جهت یافتن یک الگو در متن استفاده شده است)

              در این پروژه، حدود 3800 واژه فارسی به‌ یک smart tag انتساب داده می‌شود (در روال استاندارد ThisAddIn_Startup). سپس در هنگام نمایش آن، معادل پارسی کلمه نیز به منوی باز شده افزوده گشته و در روال رخداد کلیک آن، تعویض کلمه تشخیص داده شده با واژه پیدا شده صورت خواهد گرفت.

              در ادامه فرض بر این است که یک پروژه جدید word add-in را در VS.Net ایجاد کرده‌اید و همچنین ارجاعی را به فایل System.Data.SQLite.DLL افزوده‌اید.

              using System;
              using System.Diagnostics;
              using Microsoft.Office.Tools.Word;
              using Action = Microsoft.Office.Tools.Word.Action;

              private SmartTag _st;
              private void init()
              {
              try
              {
              //Enable Smart Tags in Word
              if (!Application.Options.LabelSmartTags)
              {
              //ممکن است اسمارت تگ‌ها در ورد غیرفعال باشند. به این صورت می‌شود آنها را فعال کرد
              Application.Options.LabelSmartTags = true;
              }

              _st = new SmartTag(@"www.microsoft.com/Demo#FarsiSmartTag", @"فارسی به پارسی");

              //دریافت واژه‌های فارسی از دیتابیس و افزودن خودکار آنها به اسمارت تگ‌ها
              if (!DBhelper.AddSmartTagItems(_st, "select distinct farsi from tblFarsiToParsi")) return;

              Action stActions = new Action("تبدیل");//تعریف یک اکشن جدید
              stActions.Click += stActions_Click;//انتساب روال‌های رخداد گردان
              stActions.BeforeCaptionShow += stActions_BeforeCaptionShow;
              _st.Actions = new[] { stActions };
              VstoSmartTags.Add(_st);//افزودن اسمارت تگ به مجموعه
              }
              catch (Exception ex)
              {
              EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
              }
              }

              private void ThisAddIn_Startup(object sender, EventArgs e)
              {
              init();
              }

              دو روال رخداد گردان زیر نیز جهت تغییر عنوان پیش فرض به واژه یافته شده در لحظه نمایش منو و روال کلیک نیز ایجاد خواهد شد:

              static void stActions_BeforeCaptionShow(object sender, ActionEventArgs e)
              {
              try
              {
              Action clickedAction = sender as Action;
              if (clickedAction != null)
              {
              string parsi = DBhelper.FindParsi(e.Text);//معادل پارسی از دیتابیس دریافت می‌شود
              clickedAction.Caption = (parsi == string.Empty ? e.Text : parsi);
              }
              }
              catch (Exception ex)
              {
              EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
              }
              }

              static void stActions_Click(object sender, ActionEventArgs e)
              {
              try
              {
              Action clickedAction = sender as Action;
              if (clickedAction != null)
              {
              e.Range.Text = clickedAction.Caption;//جایگزینی متن موجود با عنوانی که پیشتر پارسی شده است
              }
              }
              catch (Exception ex)
              {
              EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
              }
              }

              نکته‌ای را که در اینجا باید حتما رعایت کرد بحث exception handling‌ است. خصوصا در روال استاندارد ThisAddIn_Startup . اگر در این روال خطایی مدیریت نشده رخ دهد، word افزودنی شما را به صورت غیرفعال به مجموعه اضافه خواهد کرد و فعال سازی بعدی آن پس از اصلاح کد واقعا مشکل خواهد بود. همانطور که ملاحظه می‌کنید تمامی خطاها در event log‌ ویندوز نوشته می‌شوند.
              همچنین باید دقت داشت که اگر متغیری در سطح کلاس تعریف نشود به احتمال زیاد تا دقایقی بعد توسط garbage collector به دیار باقی خواهد شتافت (تعریف st_ در اینجا). اینجاست که شاید ساعت‌ها وقت صرف کنید که چرا روال‌های رخ‌داد گردان دیگر اجرا نمی‌شوند. چرا افزونه دیگر کار نمی‌کند.

              همین! کل سورس این add-in منهای بحث دریافت اطلاعات از دیتابیس همین بود! وظیفه‌ی تشخیص کلمات معرفی شده به ms-word به‌عهده‌ی خود آن است و این‌کار را نیز به‌خوبی انجام می‌دهد. در گذشته‌های نچندان دور ایجاد یک افزونه برای word واقعا مشکل بود که با این روش بسیاری از موانع برطرف شده است.

              کلاس DBHelper که کار دریافت اطلاعات واژه‌ها را از دیتابیس SQLite انجام می‌دهد به شرح زیر است:

              using System;
              using System.Data.SQLite;
              using System.Diagnostics;
              using System.Reflection;
              using Microsoft.Office.Tools.Word;

              namespace Farsi2Parsi
              {
              class DBhelper
              {
              #region Methods (2)

              // Public Methods (2)

              public static bool AddSmartTagItems(SmartTag st, string strSQL)
              {
              SQLiteDataReader myReader = null;
              SQLiteCommand sqlCmd = null;
              bool ret = false;
              try
              {
              SQLiteConnection sqlCon = new SQLiteConnection
              {
              ConnectionString = "Data Source=" + ConStr.ConnectionString
              };
              sqlCon.Open();
              sqlCmd = new SQLiteCommand(strSQL, sqlCon);
              myReader = sqlCmd.ExecuteReader();

              if (myReader != null)
              while (myReader.Read())
              {
              if (myReader.GetValue(0) != DBNull.Value)
              st.Terms.Add(myReader.GetValue(0).ToString());
              }

              ret = true;
              }
              catch (Exception ex)
              {
              EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
              Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 7);
              }
              finally
              {
              if (myReader != null)
              myReader.Close();

              if (sqlCmd != null)
              sqlCmd.Connection.Close();
              }
              return ret;
              }

              public static string FindParsi(string farsi)
              {
              SQLiteDataReader myReader = null;
              SQLiteCommand sqlCmd = null;
              string ret = string.Empty;
              string strSQL = "select parsi from tblFarsiToParsi where farsi='" + farsi.Replace("'", "''") + "'";
              try
              {
              SQLiteConnection sqlCon = new SQLiteConnection
              {
              ConnectionString = "Data Source=" + ConStr.ConnectionString
              };
              sqlCon.Open();
              sqlCmd = new SQLiteCommand(strSQL, sqlCon);
              myReader = sqlCmd.ExecuteReader();

              if (myReader != null)
              {
              myReader.Read(); //اولین مورد کافی است
              if (myReader.GetValue(0) != DBNull.Value)
              ret = myReader.GetValue(0).ToString();
              }
              }
              catch (Exception ex)
              {
              EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
              Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 8);
              }
              finally
              {
              if (myReader != null)
              myReader.Close();

              if (sqlCmd != null)
              sqlCmd.Connection.Close();
              }
              return ret;
              }
              #endregion Methods
              }
              }

              همانطور که پیشتر نیز عنوان شد اگر با ADO.net آشنایی داشته باشید، هیچ نکته‌ی خاص جدیدی را در اینجا مشاهده نخواهید کرد و تنها یک سری امور روزمره کاری با ADO.net مطرح شده است، باز کردن کانکشن، اجرای کوئری، دریافت اطلاعات و پاکسازی نهایی. (قسمت finally را با استفاده از عبارت using می‌شود حذف کرد)

              هنگام نصب برنامه، مسیر پوشه نصب در رجیستری ویندوز توسط نصاب نوشته خواهد شد. از همین مورد برای ایجاد رشته اتصالی به دیتابیس استفاده گردید.

              class ConStr
              {
              public static string ConnectionString
              {
              get
              {
              return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\FarsiToParsi").GetValue("folder") + "\\ErrorsBank.sqlite";
              }
              }
              }

              سورس کامل این افزونه را به صورت یک پروژه VS.Net 2008 SP1 از اینجا می‌توانید دریافت کنید.
              نصاب برنامه با استفاده از NSIS ایجاد شده که در روزی دیگر درباره‌ی آن توضیح خواهم داد.
              اگر قصد داشته باشید از روش‌های متداول استفاده کنید، مشاهده ویدیوی زیر توصیه می‌شود:
              http://msdn.microsoft.com/en-us/office/bb851702.aspx

              برای توزیع این نوع افزونه‌ها علاوه بر دات نت فریم ورک، به چهار به روز رسانی دیگر نیز نیاز خواهد بود:
              به روز رسانی نصاب ویندوز (که احتمالا نصب هست)
              WindowsInstaller-KB893803-v2-x86.exe
              Microsoft Office System Update: Redistributable Primary Interop Assemblies :
              o2007pia.msi
              نصب vsto و همچنین sp1 آن
              vstor30.exe
              vstor30sp1-KB949258-x86.exe

              این موارد را من در بسته به روز رسانی سیستم قرار داده‌ام که به صورت خودکار و یکی پس از دیگری اجرا و نصب خواهند شد.
              پس از آن با کلیک بر روی فایلی با پسوند vsto که در پوشه build برنامه موجود است، می‌توان افزونه را نصب کرد (click once installation).




              سایر اطلاعات در مورد پروژه‌های VSTO را می‌توان از طریق وبلاگ رسمی آنها دنبال کرد:
              http://blogs.msdn.com/vsto/

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


              مطالب
              استفاده از API ترجمه گوگل

              مطابق Ajax API ترجمه گوگل، برای ترجمه یک متن باید محتویات آدرس زیر را تحلیل کرد:
              http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q={0}&langpair={1}|{2}
              که در آن پارامتر اول، متن مورد نظر، پارامترهای 1 و 2 زبان‌های مبدا و مقصد می‌باشند. برای دریافت اطلاعات، ذکر ارجاع دهنده الزامی است (referrer)، اما ذکر کلید API گوگل اختیاری می‌باشد (که هر فرد می‌تواند کلید خاص خود را از گوگل دریافت کند).
              بنابراین برای استفاده از آن تنها کافی است این URL را تشکیل داده و سپس محتویات خروجی آن‌را آنالیز کرد. فرمت نهایی دریافت شده از نوع JSON است. برای مثال اگر hello world! را به این سرویس ارسال نمائیم،‌ خروجی نهایی JSON‌ دریافت شده به صورت زیر خواهد بود:

              //{\"responseData\": {\"translatedText\":\"سلام جهان!\"}, \"responseDetails\": null, \"responseStatus\": 200}

              در کتابخانه‌ی System.Web.Extensions.dll دات نت فریم ورک سه و نیم، کلاس JavaScriptSerializer برای این منظور پیش بینی شده است. تنها کافی است به متد Deserialize آن، متن JSON دریافتی را پاس کنیم:

              GoogleAjaxResponse result =
              new JavaScriptSerializer().Deserialize<GoogleAjaxResponse>(jsonGoogleAjaxResponse);

              برای اینکه عملیات نگاشت اطلاعات متنی JSON به کلاس‌های دات نتی ما با موفقیت صورت گیرد، می‌توان خروجی JSON گوگل را به شکل زیر نمایش داد:

              //ResponseData.cs file
              public class ResponseData
              {
              public string translatedText { get; set; }
              }

              //GoogleAjaxResponse.cs file
              using System.Net;

              /// <summary>
              /// کلاسی جهت نگاشت اطلاعات جی سون دریافتی به آن
              /// </summary>
              public class GoogleAjaxResponse
              {
              public ResponseData responseData { get; set; }
              public object responseDetails { get; set; }
              public HttpStatusCode responseStatus { get; set; }
              }
              با این توضیحات، کلاس نهایی ترجمه گوگل ما به شکل زیر خواهد بود:

              using System;
              using System.Globalization;
              using System.IO;
              using System.Net;
              using System.Web;
              using System.Web.Script.Serialization;

              //{\"responseData\": {\"translatedText\":\"سلام جهان!\"}, \"responseDetails\": null, \"responseStatus\": 200}

              public class CGoogleTranslator
              {
              #region Fields (1)

              /// <summary>
              /// ارجاع دهنده
              /// </summary>
              private readonly string _referrer;

              #endregion Fields

              #region Constructors (1)

              /// <summary>
              /// مطابق مستندات نیاز به یک ارجاع دهنده اجباری می‌باشد
              /// </summary>
              /// <param name="referrer"></param>
              public CGoogleTranslator(string referrer)
              {
              _referrer = referrer;
              }

              #endregion Constructors

              #region Properties (2)

              /// <summary>
              /// ترجمه از زبان
              /// </summary>
              public CultureInfo FromLanguage { get; set; }

              /// <summary>
              /// ترجمه به زبان
              /// </summary>
              public CultureInfo ToLanguage { get; set; }

              #endregion Properties

              #region Methods (2)

              // Public Methods (1)

              /// <summary>
              /// ترجمه متن با استفاده از موتور ترجمه گوگل
              /// </summary>
              /// <param name="data"></param>
              /// <returns></returns>
              public string TranslateText(string data)
              {
              //ساخت و انکدینگ آدرس مورد نظر
              string url =
              string.Format(
              "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q={0}&langpair={1}|{2}",
              HttpUtility.UrlEncode(data), //needs a ref. to System.Web.dll
              FromLanguage.TwoLetterISOLanguageName,
              ToLanguage.TwoLetterISOLanguageName
              );

              //دریافت اطلاعات جی سون از گوگل
              string jsonGoogleAjaxResponse = fetchWebPage(url);

              //needs a ref. to System.Web.Extensions.dll
              //نگاشت اطلاعات جی سون دریافت شده به کلاس مرتبط
              GoogleAjaxResponse result =
              new JavaScriptSerializer().Deserialize<GoogleAjaxResponse>(jsonGoogleAjaxResponse);

              if (result != null && result.responseData != null && result.responseStatus == HttpStatusCode.OK)
              {
              return result.responseData.translatedText;
              }
              return string.Empty;
              }
              // Private Methods (1)

              /// <summary>
              /// دریافت محتویات جی سون بازگشتی از گوگل
              /// </summary>
              /// <param name="url"></param>
              /// <returns></returns>
              string fetchWebPage(string url)
              {
              try
              {
              var uri = new Uri(url);
              if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
              {
              var request = WebRequest.Create(uri) as HttpWebRequest;
              if (request != null)
              {
              request.Method = WebRequestMethods.Http.Get;
              request.Referer = _referrer;
              request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.0; ; rv:1.8.0.7) Gecko/20060917 Firefox/1.9.0.1";
              request.AllowAutoRedirect = true;
              request.Timeout = 1000 * 300;
              request.KeepAlive = false;
              request.ReadWriteTimeout = 1000 * 300;
              request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

              using (var response = request.GetResponse() as HttpWebResponse)
              {
              if (response != null)
              {
              using (var reader = new StreamReader(response.GetResponseStream()))
              {
              return reader.ReadToEnd().Trim();
              }
              }
              }
              }
              }
              return string.Empty;
              }
              catch (Exception ex)
              {
              Console.WriteLine(String.Format("fetchWebPage: {0} >> {1}", ex.Message, url), true);
              return string.Empty;
              }
              }

              #endregion Methods
              }
              مثالی در مورد نحوه‌ی استفاده از آن برای ترجمه یک متن از انگلیسی به فارسی:

              string res = new CGoogleTranslator("https://www.dntips.ir/")
              {
              FromLanguage = CultureInfo.GetCultureInfo("en-US"),
              ToLanguage = CultureInfo.GetCultureInfo("fa-IR")
              }.TranslateText("Hello world!");

              مطالب
              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 در حالت پیش فرض پروژه‌های ویژوال استودیو) نیز در مسیرهای مربوطه ایجاد میگردد.
              مطالب
              Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
              در ادامه قصد داریم از سرویس زیر که در قسمت قبل تکمیل شد، در یک برنامه‌ی Blazor Server استفاده کنیم:
              namespace BlazorServer.Services
              {
                  public interface IHotelRoomService
                  {
                      Task<HotelRoomDTO> CreateHotelRoomAsync(HotelRoomDTO hotelRoomDTO);
              
                      Task<int> DeleteHotelRoomAsync(int roomId);
              
                      IAsyncEnumerable<HotelRoomDTO> GetAllHotelRoomsAsync();
              
                      Task<HotelRoomDTO> GetHotelRoomAsync(int roomId);
              
                      Task<HotelRoomDTO> IsRoomUniqueAsync(string name);
              
                      Task<HotelRoomDTO> UpdateHotelRoomAsync(int roomId, HotelRoomDTO hotelRoomDTO);
                  }
              }


              تعریف کامپوننت‌های ابتدایی نمایش لیست اتاق‌ها و ثبت و ویرایش آن‌ها


              در ابتدا کامپوننت‌های خالی نمایش لیست اتاق‌ها و همچنین فرم خالی ثبت و ویرایش آن‌ها را به همراه مسیریابی‌های مرتبط، ایجاد می‌کنیم. به همین جهت ابتدا داخل پوشه‌ی Pages، پوشه‌ی جدید HotelRoom را ایجاد کرده و فایل جدید HotelRoomList.razor را با محتوای ابتدایی زیر، به آن اضافه می‌کنیم.
              @page "/hotel-room"
              
              <div class="row mt-4">
                  <div class="col-8">
                      <h4 class="card-title text-info">Hotel Rooms</h4>
                  </div>
                  <div class="col-3 offset-1">
                      <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink>
                  </div>
              </div>
              
              @code {
              
              }
              این کامپوننت در مسیر hotel-room/ قابل دسترسی خواهد بود. بر این اساس، به کامپوننت Shared\NavMenu.razor مراجعه کرده و مدخل منوی آن‌را تعریف می‌کنیم:
              <li class="nav-item px-3">
                  <NavLink class="nav-link" href="hotel-room">
                      <span class="oi oi-list-rich" aria-hidden="true"></span> Hotel Rooms
                  </NavLink>
              </li>

              تا اینجا صفحه‌ی ابتدایی نمایش لیست اتاق‌ها، به همراه یک دکمه‌ی افزودن اتاق جدید نیز هست. به همین جهت فایل جدید Pages\HotelRoom\HotelRoomUpsert.razor را به همراه مسیریابی hotel-room/create/ برای تعریف کامپوننت ابتدایی ثبت و ویرایش اطلاعات اتاق‌ها، اضافه می‌کنیم:
              @page "/hotel-room/create"
              
              <h3>HotelRoomUpsert</h3>
              
              @code {
              
              }
              - واژه‌ی Upsert در مورد فرمی بکاربرده می‌شود که هم برای ثبت اطلاعات و هم برای ویرایش اطلاعات از آن استفاده می‌شود.
              - NavLink تعریف شده‌ی در کامپوننت نمایش لیست اتاق‌ها، به مسیریابی کامپوننت فوق اشاره می‌کند.


              ایجاد فرم ثبت یک اتاق جدید

              برای ثبت یک اتاق جدید نیاز است به مدل UI آن که همان HotelRoomDTO تعریف شده‌ی در قسمت قبل است، دسترسی داشت. به همین جهت در پروژه‌ی BlazorServer.App، ارجاعی را به پروژه‌ی BlazorServer.Models.csproj اضافه می‌کنیم:
              <Project Sdk="Microsoft.NET.Sdk.Web">
                <ItemGroup>
                  <ProjectReference Include="..\BlazorServer.Models\BlazorServer.Models.csproj" />
                </ItemGroup>
              </Project>
              سپس جهت سراسری اعلام کردن فضای نام آن، یک سطر زیر را به انتهای فایل BlazorServer.App\_Imports.razor اضافه می‌کنیم:
              @using BlazorServer.Models
              اکنون می‌توانیم کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor را به صورت زیر تکمیل کنیم:
              @page "/hotel-room/create"
              
              <div class="row mt-2 mb-5">
                  <h3 class="card-title text-info mb-3 ml-3">@Title Hotel Room</h3>
                  <div class="col-md-12">
                      <div class="card">
                          <div class="card-body">
                              <EditForm Model="HotelRoomModel">
                                  <div class="form-group">
                                      <label>Name</label>
                                      <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
                                  </div>
                              </EditForm>
                          </div>
                      </div>
                  </div>
              </div>
              
              @code
              {
                  private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();
                  private string Title = "Create";
              }
              توضیحات:
              - در برنامه‌های Blazor، کامپوننت ویژه‌ی EditForm را بجای تگ استاندارد form، مورد استفاده قرار می‌دهیم.
              - این کامپوننت، مدل فرم را از فیلد HotelRoomModel که در قسمت کدها تعریف کردیم، دریافت می‌کند. کار آن تامین اطلاعات فیلدهای فرم است.
              - سپس در EditForm تعریف شده، بجای المان استاندارد input، از کامپوننت InputText برای دریافت اطلاعات متنی استفاده می‌شود. با bind-value@ در قسمت چهارم این سری بیشتر آشنا شدیم و کار آن two-way data binding است. در اینجا هر اطلاعاتی که وارد می‌شود، سبب به روز رسانی خودکار مقدار خاصیت HotelRoomModel.Name می‌شود و برعکس.

              یک نکته: در قسمت قبل، مدل UI را از نوع رکورد C# 9.0 و init only تعریف کردیم. رکوردها، با EditForm و two-way databinding آن سازگاری ندارند (bind-value@ در اینجا) و بیشتر برای کنترلرهای برنامه‌های Web API که یکبار قرار است کار وهله سازی آن‌ها در زمان دریافت اطلاعات از کاربر صورت گیرد، مناسب هستند و نه با فرم‌های پویای Blazor. به همین جهت به پروژه‌ی BlazorServer.Models مراجعه کرده و نوع آن‌ها را به کلاس و init‌ها را به set معمولی تغییر می‌دهیم تا در فرم‌های Blazor هم قابل استفاده شوند.

              تا اینجا کامپوننت ثبت اطلاعات یک اتاق جدید، چنین شکلی را پیدا کرده‌است:



              تکمیل سایر فیلدهای فرم ورود اطلاعات اتاق

              پس از تعریف فیلد ورود اطلاعات نام اتاق، سایر فیلدهای متناظر با HotelRoomDTO را نیز به صورت زیر به EditForm تعریف شده اضافه می‌کنیم که در اینجا از InputNumber برای دریافت اطلاعات عددی و از InputTextArea، برای دریافت اطلاعات متنی چندسطری استفاده شده‌است:
              <EditForm Model="HotelRoomModel">
                  <div class="form-group">
                      <label>Name</label>
                      <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
                  </div>
                  <div class="form-group">
                      <label>Occupancy</label>
                      <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber>
                  </div>
                  <div class="form-group">
                      <label>Rate</label>
                      <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber>
                  </div>
                  <div class="form-group">
                      <label>Sq ft.</label>
                      <InputText @bind-Value="HotelRoomModel.SqFt" class="form-control"></InputText>
                  </div>
                  <div class="form-group">
                      <label>Details</label>
                      <InputTextArea @bind-Value="HotelRoomModel.Details" class="form-control"></InputTextArea>
                  </div>
                  <div class="form-group">
                      <button class="btn btn-primary">@Title Room</button>
                      <NavLink href="hotel-room" class="btn btn-secondary">Back to Index</NavLink>
                  </div>
              </EditForm>
              با این خروجی:



              تعریف اعتبارسنجی‌های فیلدهای یک فرم Blazor

              در حین تعریف یک فرم، برای واکنش نشان دادن به دکمه‌ی submit، می‌توان رویداد OnSubmit را به کامپوننت EditForm اضافه کرد که سبب فراخوانی متدی در قسمت کدهای کامپوننت جاری خواهد شد؛ مانند فراخوانی متد HandleHotelRoomUpsert در مثال زیر:
              <EditForm Model="HotelRoomModel" OnSubmit="HandleHotelRoomUpsert">
              </EditForm>
              
              @code
              {
                  private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();
              
                  private async Task HandleHotelRoomUpsert()
                  {
              
                  }
              }
              هرچند HotelRoomDTO تعریف شده به همراه تعریف اعتبارسنجی‌هایی مانند Required است، اما اگر بر روی دکمه‌ی submit کلیک کنیم، متد HandleHotelRoomUpsert فراخوانی می‌شود. یعنی روال رویدادگردان OnSubmit، صرفنظر از وضعیت اعتبارسنجی مدل فرم، همواره با submit فرم، اجرا می‌شود.
              اگر این مورد، مدنظر نیست، می‌توان بجای OnSubmit، از رویداد OnValidSubmit استفاده کرد. در این حالت اگر اعتبارسنجی مدل فرم با شکست مواجه شود، دیگر متد HandleHotelRoomUpsert فراخوانی نخواهد شد. همچنین در این حالت می‌توان خطاهای اعتبارسنجی را نیز در فرم نمایش داد:
              <EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert">
                  <DataAnnotationsValidator />
                  @*<ValidationSummary />*@
                  <div class="form-group">
                      <label>Name</label>
                      <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
                      <ValidationMessage For="()=>HotelRoomModel.Name"></ValidationMessage>
                  </div>
                  <div class="form-group">
                      <label>Occupancy</label>
                      <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber>
                      <ValidationMessage For="()=>HotelRoomModel.Occupancy"></ValidationMessage>
                  </div>
                  <div class="form-group">
                      <label>Rate</label>
                      <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber>
                      <ValidationMessage For="()=>HotelRoomModel.RegularRate"></ValidationMessage>
                  </div>
              - در اینجا قسمت‌های تغییر کرده را مشاهده می‌کنید که به همراه درج DataAnnotationsValidator و ValidationMessage‌ها است.
              - کامپوننت DataAnnotationsValidator، اعتبارسنجی مبتنی بر data annotations را مانند [Required]، در دامنه‌ی دید یک EditForm فعال می‌کند.
              - اگر خواستیم تمام خطاهای اعتبارسنجی را به صورت خلاصه‌ای در بالای فرم نمایش دهیم، می‌توان از کامپوننت ValidationSummary استفاده کرد.
              - و یا اگر خواستیم خطاها را به صورت اختصاصی‌تری ذیل هر تکست‌باکس نمایش دهیم، می‌توان از کامپوننت ValidationMessage کمک گرفت. خاصیت For آن از نوع <Expression<System.Func تعریف شده‌است که اجازه‌ی تعریف strongly typed نام خاصیت در حال اعتبارسنجی را به صورتی که مشاهده می‌کنید، میسر می‌کند.



              ثبت اولین اتاق هتل

              در ادامه می‌خواهیم روال رویدادگردان HandleHotelRoomUpsert را مدیریت کنیم. به همین جهت نیاز به کار با سرویس IHotelRoomService ابتدای بحث خواهد بود. بنابراین در ابتدا به فایل BlazorServer.App\_Imports.razor مراجعه کرده و فضای نام سرویس‌های برنامه را اضافه می‌کنیم:
              @using BlazorServer.Services
              اکنون امکان تزریق IHotelRoomService را که در قسمت قبل پیاده سازی و به سیستم تزریق وابستگی‌های برنامه معرفی کردیم، پیدا می‌کنیم:
              @page "/hotel-room/create"
              
              @inject IHotelRoomService HotelRoomService
              @inject NavigationManager NavigationManager
              
              
              @code
              {
                  private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();
                  private string Title = "Create";
              
                  private async Task HandleHotelRoomUpsert()
                  {
                      var roomDetailsByName = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name);
                      if (roomDetailsByName != null)
                      {
                          //there is a duplicate room. show an error msg.
                          return;
                      }
              
                      var createdResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel);
                      NavigationManager.NavigateTo("hotel-room");
                  }
              }
              در اینجا در ابتدا، سرویس IHotelRoomService به کامپوننت جاری تزریق شده و سپس از متدهای IsRoomUniqueAsync و CreateHotelRoomAsync آن، جهت بررسی منحصربفرد بودن نام اتاق و ثبت نهایی اطلاعات مدل برنامه که به فرم جاری به صورت دو طرفه‌ای متصل است، استفاده کرده‌ایم. در نهایت پس از ثبت اطلاعات، کاربر به صفحه‌ی نمایش لیست اتاق‌ها، توسط سرویس توکار NavigationManager، هدایت می‌شود.

              اگر پیشتر با ASP.NET Web Forms کار کرده باشید (اولین روش توسعه‌ی برنامه‌های وب در دنیای دات نت)، مدل برنامه نویسی Blazor Server، بسیار شبیه به کار با وب فرم‌ها است؛ البته بر اساس آخرین تغییرات دنیای دانت نت مانند برنامه نویسی async، کار با سرویس‌ها، تزریق وابستگی‌های توکار و غیره.


              نمایش لیست اتاق‌های ثبت شده


              تا اینجا موفق شدیم اطلاعات یک مدل اعتبارسنجی شده را در بانک اطلاعاتی ثبت کنیم. مرحله‌ی بعد، نمایش لیست اطلاعات ثبت شده‌ی در بانک اطلاعاتی است. بنابراین به کامپوننت HotelRoomList.razor مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
              @page "/hotel-room"
              
              @inject IHotelRoomService HotelRoomService
              
              <div class="row mt-4">
                  <div class="col-8">
                      <h4 class="card-title text-info">Hotel Rooms</h4>
                  </div>
                  <div class="col-3 offset-1">
                      <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink>
                  </div>
              </div>
              
              <div class="row mt-4">
                  <div class="col-12">
                      <table class="table table-bordered table-hover">
                          <thead>
                              <tr>
                                  <th>Name</th>
                                  <th>Occupancy</th>
                                  <th>Rate</th>
                                  <th>
                                      Sqft
                                  </th>
                                  <th>
              
                                  </th>
                              </tr>
                          </thead>
                          <tbody>
                              @if (HotelRooms.Any())
                              {
                                  foreach (var room in HotelRooms)
                                  {
                                      <tr>
                                          <td>@room.Name</td>
                                          <td>@room.Occupancy</td>
                                          <td>@room.RegularRate.ToString("c")</td>
                                          <td>@room.SqFt</td>
                                          <td></td>
                                      </tr>
                                  }
                              }
                              else
                              {
                                  <tr>
                                      <td colspan="5">No records found</td>
                                  </tr>
                              }
                          </tbody>
                      </table>
                  </div>
              </div>
              
              @code
              {
                  private List<HotelRoomDTO> HotelRooms = new List<HotelRoomDTO>();
              
                  protected override async Task OnInitializedAsync()
                  {
                      await foreach(var room in HotelRoomService.GetAllHotelRoomsAsync())
                      {
                          HotelRooms.Add(room);
                      }
                  }
              }
              توضیحات:
              - متد GetAllHotelRoomsAsync، لیست اتاق‌های ثبت شده را بازگشت می‌دهد. البته خروجی آن از نوع <IAsyncEnumerable<HotelRoomDTO است که از زمان C# 8.0 ارائه شد و روش کار با آن اندکی متفاوت است. IAsyncEnumerable‌ها را باید توسط await foreach پردازش کرد.
              - همانطور که در مطلب بررسی چرخه‌ی حیات کامپوننت‌ها نیز عنوان شد، متدهای رویدادگران OnInitialized و نمونه‌ی async آن برای دریافت اطلاعات از سرویس‌ها طراحی شده‌اند که در اینجا نمونه‌ای از آن‌را مشاهده می‌کنید.
              - پس از تشکیل لیست اتاق‌ها، حلقه‌ی foreach (var room in HotelRooms) تعریف شده، ردیف‌های آن‌را در UI نمایش می‌دهد.


              کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-14.zip
              مطالب
              لینک‌های هفته اول دی

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

              امنیت



              ASP. Net


              طراحی وب

              PHP


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


              سی شارپ


              عمومی دات نت


              مسایل اجتماعی و انسانی برنامه نویسی


              کتاب‌های رایگان جدید


              متفرقه
              • آهن بجای کروم! (یک برنامه نویس آلمانی قسمت‌هایی از مرورگر کروم را که در جهت جمع آوری اطلاعات برای گوگل بکار می‌رفته، حذف کرده و مرورگر دیگری به نام آهن را ارائه داده است!)