مطالب
توسعه اپلیکیشن‌های Node.js در ویژوال استودیو
آشنایی با Node.js

Node.js یک پلت‌فرم جاوا اسکریپتی سمت سرور است که ابتدا توسط Ryan Dahl در سال 2009 معرفی گردید. از Node.js جهت ساخت اپلیکیشن‌های مقیاس‌پذیر تحت شبکه و با زبان برنامه‌نویسی جاوا اسکریپت در سمت سرور استفاده می‌شود. Node.js در پشت صحنه از ران‌تایم V8 استفاده می‌کند؛ یعنی همان ران‌تایمی که درون مرورگر کروم استفاده شده است. Node.js در واقع یک wrapper برای این موتور V8 است؛ جهت ارائه‌ی قابلیت‌های بیشتری برای ایجاد برنامه‌های تحت شبکه. یکی از مزایای Node.js سریع بودن آن است. دلیل آن نیز این است که به صورت کامل توسط کدهای C تهیه شده است (البته می‌توانید در این آدرس benchmark مربوط به ASP.NET Core 1.0 و مقایسه‌ی آن با دیگر پلت‌فرم‌ها را نیز بررسی کنید).

چه نوع اپلیکیشن‌های را می‌توان با Node.js توسعه داد؟

  • سرور WebSocket جهت توسعه‌ی اپلیکیشن‌های بلادرنگ
  • فایل آپلودر سریع در سمت کلاینت
  • Ad Server
  • و ...
لازم به ذکر است، Node.js یک فریم‌ورک تحت وب نیست و همچنین قرار نیست یک جایگزین برای دیگر فریم‌ورک‌ها مانند ASP.NET MVC و... باشد. در حالت کلی هدف آن انجام یک‌سری اعمال سطح پائین شبکه‌ایی است. البته کتابخانه‌هایی برفراز Node.js نوشته شده‌اند که آن را تبدیل به یک وب‌فریم‌ورک خواهند کرد (+).
قبل از شروع به کار با Node.js، باید تفاوت blocking code و non-blocking code را بدانید. فرض کنید قرار است محتویات یک فایل را در خروجی نمایش دهیم. در حالت اول یعنی blocking code، باید ابتدا فایل را از فایل سیستم بخوانیم و آن را به یک متغیر انتساب دهیم و در نهایت محتویات متغیر را در خروجی چاپ کنیم. در این‌حالت تا زمانیکه فایل از فایل سیستم خوانده نشود، نمی‌توان محتویات آن را در خروجی نمایش داد. اما در حالت non-blocking فایل را از فایل سیستم می‌خوانیم و هر زمانیکه عملیات خواندن فایل به اتمام رسید می‌توانیم محتویات فایل را در خروجی نمایش دهیم. یعنی برخلاف حالت قبل، در این روش بلاک شدن کد به ازای عملیات زمانبر را نخواهیم داشت. در این روش عبارت هر زمانیکه عملیات خواندن فایل به اتمام رسید یک callback تلقی می‌شود. یعنی تا وقتیکه فایل از فایل سیستم خوانده شود، به دیگر عملیات رسیدگی خواهیم کرد و به محض اتمام خوانده شدن فایل، عملیات نمایش در خروجی را فراخوانی خواهیم کرد. در نتیجه برای حالت blocking این چنین کدی را خواهیم داشت:
var contents = fs.readFileSync('filePath');
console.log(content);
console.log('Doing something else');
همچنین برای حالت non-blocking نیز این چنین کدی را خواهیم داشت:
fs.readFile('filePath', function (err, contents) {
   console.log(contents);
});
console.log('Doing something else');

برای شروع به کار با Node.js می‌توانید با مراجعه به وب‌سایت رسمی آن، آن‌را دانلود و بر روی سیستم خود نصب کنید. بعد از نصب Node می‌توانیم از طریق command line وارد shell آن شوید و دستورات جاوا اسکریپتی خود را اجرا نمائید:


احتمالاً به این نوع استفاده‌ی از Node.js که به REPL معروف است، نیازی نداشته باشید. در واقع هدف بررسی نصب بودن ران‌تایم بر روی سیستم است. با استفاده از فرمان node نیز می‌توان یک فایل جاوا اسکریپتی را اجرا کرد. برای اینکار یک فایل با نام test.js را با محتویات زیر درون VS Code ایجاد کنید:



سپس دستور node test.js را وارد کنید:



همانطور که مشاهده می‌کنید نتیجه‌ی فایل عنوان شده، در خروجی نمایش داده شده است. در حالت کلی تمام کاری که نود انجام می‌دهد، ارائه یک Execution engine برای جاوا اسکریپت می‌باشد. 


استفاده از Node.js در ویژوال استودیو


برای کار با Node.js درون ویژوال استودیو باید ابتدا افزونه‌ی Node.js Tools را برای ویژوال استودیو نصب کنید. بعد از نصب این افزونه‌، تمپلیت Node.js در زمان ایجاد یک پروژه برای شما نمایش داده خواهد شد:

 


برای شروع، تمپلیت Blank Node.js Console Application را انتخاب کرده و بر روی OK کلیک کنید. با اینکار یک پروژه با ساختار زیر برایمان ایجاد خواهد شد: 



همانطور که ملاحظه می‌کنید، یک فایل با نام app.js درون تمپلیت ایجاد شده، موجود است. app.js در واقع نقطه‌ی شروع برنامه‌‌مان خواهد بود. همچنین دو فایل دیگر نیز با نام‌های README.md، جهت افزودن توضیحات و یک فایل با نام package.json، جهت مدیریت وابستگی‌های برنامه به پروژه اضافه شده‌اند. اکنون می‌توانیم شروع به توسعه‌ی برنامه‌ی خود درون ویژوال استودیو کنیم. همچنین می‌توانیم از قابلیت‌های debugging ویژوال استودیو نیز بهره ببریم: 



اگر مسیر پروژه‌ی ایجاد شده‌ی فوق را درون windows explorer باز کنید خواهید دید که ساختار آن شبیه به یک پروژه‌ی Node.js می‌باشد. با این تفاوت که دو آیتم دیگر همانند دیگر پروژه‌های ویژوال استودیو نیز به آن اضافه شده است که طبیعتاً می‌توانید در حین کار با سورس کنترل، از انتشار آنها صرفنظر کنید.



لازم به ذکر است پروژه‌ی ایجاد شده‌ی فوق را نیز می‌توانید همانند حالت عادی، از طریق command line و همانند پروژه‌های Node.js اجرا کنید: 

node app.js


در واقع از ویژوال استدیو می‌توانیم به عنوان یک ابزار برای دیباگ پروژه‌های Node.js استفاده کنیم. لازم به ذکر است، Visual Studio Code نیز امکان دیباگ اپلیکیشن‌های Node.js را در اختیارمان قرار می‌دهد. در نتیجه در مواقعیکه نسخه‌ی کامل ویژوال استودیو در دسترس نیست نیز می‌توانیم از VS Code برای دیباگ برنامه‌هایمان استفاده کنیم:


مطالب
بهبود SEO برنامه‌های Angular
یکی از موارد مهم بهینه سازی صفحات سایت برای موتورهای جستجو، افزودن عنوانی مناسب، به همراه توضیحات و واژه‌های کلیدی، twitter card ،Facebook Graph و امثال آن‌ها است. برای این منظور Angular به همراه سرویس‌هایی است که امکان افزودن این متاتگ‌ها را به صورت پویا مهیا می‌کنند.


آشنایی با امکانات بسته‌ی angular/platform-browser@

در ماژول angular/platform-browser@، دو سرویس Meta و Title، امکان تغییر پویای متاتگ‌های صفحه‌ی جاری را مهیا می‌کنند. برای نمونه فرض کنید قصد دارید یک چنین متاتگ‌هایی را به صفحه‌ی جاری اضافه کنید:
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <title>newTitle ...</title>
    <base href="/">
    <meta name="description" content="Angular meta service">
<meta name="author" content="DNT">
<meta name="keywords" content="Angular, Meta Service">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@my_site">
<meta name="twitter:title" content="Front-end Web Development">
<meta name="twitter:description" content="Learn frontend web development...">
<meta name="twitter:image" content="https://site/images/image.png">
<meta name="author" content="Other Author">
</head>
قدم اول انجام اینکار، تزریق سرویس‌های توکار Meta و Title به سازنده‌ی کامپوننت جاری است:
import { Component, OnInit } from "@angular/core";
import { Meta, Title } from "@angular/platform-browser";

@Component({
  selector: "app-seo-tests",
  templateUrl: "./seo-tests.component.html",
  styleUrls: ["./seo-tests.component.css"]
})
export class SeoTestsComponent implements OnInit {

  constructor(private metaService: Meta, private titleService: Title) {
  }
در ادامه متدهای مختلف این سرویس‌ها را بررسی خواهیم کرد:

افزودن یک یا چند متاتگ

متد addTag سرویس Meta، کار افزودن پویای یک متا تگ جدید را به همراه ویژگی‌های name و content آن، انجام می‌دهد. در ذیل چندین مثال از آن‌را مشاهده می‌کنید. در اینجا یا می‌توان از متد addTag استفاده کرد که تنها یک متاتگ را به صفحه اضافه می‌کند و یا از متد addTags کمک گرفت که می‌تواند آرایه‌ای از متاتگ‌ها را به صورت پویا به صفحه‌ی جاری اضافه کند:
    // addTag & addTags
    this.metaService.addTag({ name: "description", content: "How to optimize your Angular App for search engine and other crawlers." });
    this.metaService.addTag({ name: "author", content: "DNT" });
    this.metaService.addTag({ name: "keywords", content: "Angular, Meta Service" });
    // Or
    this.metaService.addTags([
      { name: "description", content: "How to optimize your Angular App for search engine and other crawlers." },
      { name: "author", content: "DNT" },
      { name: "keywords", content: "Angular, Meta Service" }
    ], false); // --> forceCreation = false

    this.metaService.addTag({ name: "twitter:card", content: "summary_large_image" });
    this.metaService.addTag({ name: "twitter:site", content: "@my_site" });
    this.metaService.addTag({ name: "twitter:title", content: "Front-end Web Development" });
    this.metaService.addTag({ name: "twitter:description", content: "Learn frontend web development..." });
    this.metaService.addTag({ name: "twitter:image", content: "https://site/images/image.png" });
    // Or
    this.metaService.addTags([
      { name: "twitter:card", content: "summary_large_image" },
      { name: "twitter:site", content: "@my_site" },
    ], false); // --> forceCreation = false
هر دو متد addTag و addTags دارای پارامتر boolean دومی به نام forceCreation نیز هستند. برای مثال اگر این پارامتر را به true تنظیم کنید، این متاتگ حتی اگر وجود هم داشته باشد، یکبار دیگر به صفحه اضافه خواهد شد.


دریافت محتوای متاتگ‌های موجود

با استفاده از متد getTag می‌توان یک متاتگ مشخص را به صورت HTMLMetaElement دریافت کرد:
    // getTag & getTags
    const viewport = this.metaService.getTag("name=viewport");
    if (viewport) {
      console.log(viewport.content); // width=device-width, initial-scale=1
    }
    const author = this.metaService.getTag("name=author");
    if (author) {
      console.log(author.content); // DNT
    }

    this.metaService.addTag({ name: "author", content: "DNT" });
    this.metaService.addTag({ name: "author", content: "Other Author" }, true);
    const authors = this.metaService.getTags("name=author");
    console.log(authors[0]); // <meta name="author" content="DNT">
    console.log(authors[1]); // <meta name="author" content="Other Author">
کار متد getTags بازگشت تمام متاتگ‌هایی با attribute-selector یکسان است. برای مثال در اینجا دوبار متاتگ author به صفحه اضافه شده‌است و خروجی getTags به همراه دو عنصر است.


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

می‌توان از متد updateTag برای تغییر محتوای متاتگی موجود، استفاده کرد:
    // updateTag
    this.metaService.addTag({ name: "twitter:card", content: "summary_large_image" });
    this.metaService.updateTag({ name: "twitter:card", content: "summary" }, `name='twitter:card'`);

    this.metaService.updateTag({ name: "description", content: "Angular meta service" });
در اینجا اگر پارامتر اختیاری دوم ذکر نشود، جستجوی یافتن عناصر، بر اساس name ذکر شده صورت می‌گیرد و سپس content آن‌ها به روز می‌شود.


حذف تگ‌های موجود

در اینجا می‌توان از دو متد removeTag که یک attribute-selector را دریافت می‌کند و یا removeTagElement که یک HTMLMetaElement را توسط متد getTag دریافت می‌کند، برای حذف کامل این تگ‌ها استفاده کرد:
    // removeTag & removeTagElement
    this.metaService.removeTag("charset");
    // Or
    const chartsetTag = this.metaService.getTag("charset");
    if (chartsetTag) {
      this.metaService.removeTagElement(chartsetTag);
    }


تنظیم عنوان صفحه‌ی جاری

سرویس توکار دیگری به نام Title امکان تغییر عنوان صفحه‌ی جاری را به صورت پویا میسر می‌کند:
    // Setting the browser page Title in an Angular app
    const currentTitle = this.titleService.getTitle();
    console.log(currentTitle);
    this.titleService.setTitle("newTitle ...");
متد getTitle، عنوان فعلی صفحه را باز می‌گرداند و متد setTitle، این عنوان را به روز رسانی می‌کند.


طراحی سرویسی برای افزودن پویای متاتگ‌ها به صفحات مختلف سایت

می‌توان شبیه به مطلب «نمایش Breadcrumbs در برنامه‌های Angular» به قسمت data مسیریابی، اطلاعات عنوان صفحه و همچنین  metaTags آن‌را اضافه کرد:
const routes: Routes = [
  {
    path: "seo", component: SeoTestsComponent,
    data: {
      title: "Page Title",
      metaTags: {
        description: "Page Description or some content here",
        keywords: "some, keywords, here, separated, by, a comma"
      }
    }
  }
];
سپس سرویسی را طراحی نمود که با پایان یافتن مسیریابی فعلی، این تنظیمات را به صورت خودکار انجام دهد و نیازی نباشد تا مدام به تمام کامپوننت‌ها، سرویس‌های Meta و Title را به صورت دستی اضافه کرد و این اطلاعات را تغییر داد.
به همین جهت سرویس SEO را در مسیر src\app\core\seo-service.ts به صورت ذیل ایجاد می‌کنیم:
import { Injectable } from "@angular/core";
import { Title, Meta } from "@angular/platform-browser";
import { Router, NavigationEnd, ActivatedRouteSnapshot } from "@angular/router";

@Injectable()
export class SeoService {

  constructor(private titleService: Title, private metaService: Meta, private router: Router) { }

  enableSeo() {
    this.router.events
      .filter(event => event instanceof NavigationEnd)
      .distinctUntilChanged()
      .subscribe(() => {
        this.addMetaData(this.router.routerState.snapshot.root);
      });
  }

  private addMetaData(root: ActivatedRouteSnapshot): void {
    if (root.children && root.children.length) {
      this.addMetaData(root.children[0]);
    } else if (root.data) {
      this.setTitle(root.data);
      this.setMetaTags(root.data);
    }
  }

  private setMetaTags(routeData: { [name: string]: any; }) {
    const routeDataMetaTagsKey = "metaTags";
    const metaTags = routeData[routeDataMetaTagsKey];
    if (!metaTags) { return; }
    for (const tag in metaTags) {
      if (metaTags.hasOwnProperty(tag)) {
        const newTag = { name: tag, content: metaTags[tag] };
        console.log("new tag", newTag);
        this.metaService.addTag(newTag);
      }
    }
  }

  private setTitle(routeData: { [name: string]: any; }) {
    const routeDataTitleKey = "title";
    const title = routeData[routeDataTitleKey];
    if (title) {
      console.log("new title", title);
      this.titleService.setTitle(title);
    }
  }
}
توضیحات:
در اینجا در ابتدای کار مشترک رخ‌داد NavigationEnd سیستم مسیریابی خواهیم شد:
    this.router.events
      .filter(event => event instanceof NavigationEnd)
      .distinctUntilChanged()
      .subscribe(() => {
        this.addMetaData(this.router.routerState.snapshot.root);
      });
هر زمانیکه رخ‌داد مرور صفحه‌ی جاری به پایان رسید، بر اساس مسیر ریشه‌ی آن، متد addMetaData فراخوانی می‌شود. این متد، یک متد بازگشتی است. از این جهت که مسیر جاری می‌تواند حاصل مرور یک مسیر والد و سپس چندین مسیر تو در توی فرزند و والد آن باشد.
سپس در این متد خاصیت data مسیرنهایی را خوانده و کلیدهای title و metaTags آن‌را استخراج می‌کنیم و سپس توسط متدهای this.titleService.setTitle و this.metaService.addTag، این عنوان و متاتگ‌های جدید را به صورت پویا به صفحه اضافه خواهیم کرد.

پس از تعریف این سرویس، برای معرفی آن به برنامه، ابتدا آن‌را به قسمت providers مربوط به CoreModule اضافه می‌کنیم:
import { SeoService } from "./seo-service";

@NgModule({
  providers: [
    SeoService
  ]
})
export class CoreModule {}
و در آخر به فایل app.component.ts مراجعه کرده و این سرویس را فعالسازی می‌کنیم:
import { SeoService } from "./core/seo-service";

export class AppComponent {
  constructor(private seoService: SeoService) {
    this.seoService.enableSeo();
  }
}
از این پس تمام مسیرهای برنامه به صورت خودکار تحت نظر قرار گرفته و درصورتیکه خاصیت data آن‌ها دارای کلیدهای title و metaTags باشند، به صورت خودکار پردازش خواهند شد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب دوره‌ها
متدهای الحاقی و ترکیب کننده‌های اعمال غیرهمزمان
تعدادی متد جدید در دات نت 4.5 جهت ترکیب و کار با Taskها اضافه شده‌اند. نمونه‌ای از آن‌را در قسمت‌های قبل با معرفی متد WhenAll مشاهده کردید. در ادامه قصد داریم این متدها را  بیشتر بررسی کنیم.


متد WhenAll
کار آن ترکیب تعدادی Task است و اجرای آن‌ها. تنها زمانی خاتمه می‌یابد که کلیه‌ی Taskهای معرفی شده به آن خاتمه یافته باشند. هدف از آن اجرای همزمان و مستقل چندین Task است. برای مثال دریافت چندین فایل به صورت همزمان از اینترنت.
همچنین باید دقت داشت که در اینجا، هر Task کاری به نتایج Taskهای دیگر ندارد و کاملا مستقل اجرا می‌شود. اگر نیاز است Taskها مستقل اجرا شوند، از همان روش سریالی اجرای Taskها، توسط معرفی هر کدام به کمک await استفاده کنید.
به علاوه اگر در این بین استثنایی وجود داشته باشد، تنها پس از پایان عملیات تمام Taskها بازگشت داده می‌شود. این استثناء نیز از نوع Aggregate Exception است.
using System.Linq;
using System.Threading.Tasks;

namespace Async07
{
    public class EggBoiler
    {
        private const int BoilingTimeMs = 200;

        private static Task boilEgg()
        {
            var bolingTask = Task.Run(() =>
            {
                Task.Delay(BoilingTimeMs);
            });
            return bolingTask;
        }

        public async Task BoilEggsSequentialAsync(int count)
        {
            for (var i = 0; i < count; i++)
            {
                await boilEgg();
            }
        }

        public async Task BoilEggsSimultaneousAsync(int count)
        {
            var tasksList = from egg in new[] { 1, 2, 3, 4, 5 }
                            select boilEgg();
            await Task.WhenAll(tasksList);
            // ...
        }
    }
}
در این مثال عمل پختن تخم مرغ را در یک مدت زمان مشخصی ملاحظه می‌کنید. در متد BoilEggsSequentialAsync، پختن تخم مرغ‌ها، ترتیبی است. ابتدا مورد اول انجام می‌شود و پس از پایان آن، مورد دوم و الی آخر. در اینجا اگر نیاز باشد، می‌توان از نتیجه‌ی عملیات قبلی، در عملیات بعدی استفاده کرد.
 اما در متد BoilEggsSimultaneousAsync به علت بکارگیری Task.WhenAll پختن تمام تخم مرغ‌های مدنظر همزمان آغاز می‌شود و تا پایان عملیات (پخته شدن تمام تخم مرغ‌ها) صبر خواهد شد.


متد WhenAny

در حالت استفاده از متد WhenAny، هر کدام از Taskهای در حال پردازش که خاتمه یابند، کل عملیات خاتمه خواهد یافت. فرض کنید نیاز دارید تا دمای کنونی هوای منطقه‌ی خاصی را از چند وب سرویس مختلف دریافت کنید. می‌توان در این حالت تمام این‌ها را توسط WhenAny ترکیب کرد و هر کدام که زودتر خاتمه یابد، عملیات را پایان خواهد داد.
    public class Downloader
    {
        private Task<string> downloadTask(string url)
        {
            return new WebClient().DownloadStringTaskAsync(url);
        }

        public async Task<int> GetTemperature()
        {
            var sites = new[]
            {
                "http://www.site1.com/svc",
                "http://www.site2.com/svc",
                "http://www.site3.com/svc",
            };
            var tasksList = from site in sites
                            select downloadTask(site);
            try
            {
                var finishedTask = await Task.WhenAny(tasksList);
                var result = await finishedTask;

            }
            catch (Exception ex)
            {

            }

            // todo: process result, get temperature
            return 10; // for example.
        }
    }
در اینجا نحوه‌ی استفاده از WhenAny را مشاهده می‌کنید. نکته‌ی مهم این مثال، استفاده از await دوم بر روی Task بازگشت داده شده‌است. این مساله از این لحاظ مهم است که Task بازگشت داده شده الزامی ندارد که حتما با موفقیت پایان یافته باشد. فراخوانی await بر روی نتیجه‌ی آن سبب خواهد شد تا اگر استثنایی در این بین رخ داده باشد، قابل دریافت و پردازش شود.
در این حالت اگر نیاز بود وضعیت سایر Taskها، مثلا در صورت شکست آن‌ها، بررسی شوند، می‌توان از یکی از دو قطعه کد زیر استفاده کرد:
            foreach (var task in tasksList)
            {
                var ignored = task.ContinueWith(
                    t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
            }

            // or
            foreach (var task in tasksList)
            {
                var ignored = task.ContinueWith(
                    t =>
                    {
                        if (t.IsFaulted)
                            Console.WriteLine(t.Exception);
                    });
            }

کاربرد دیگر WhenAny زمانی است که برای مثال می‌خواهید تعداد زیادی Url را پردازش کنید، اما نمی‌خواهید برای نمایش اطلاعات، تا پایان عملیات تمامی آن‌ها مانند WhenAll صبر کنید. می‌خواهید به محض پایان کار یکی از Taskها، عملیات نمایش نتیجه‌ی آن‌را انجام دهید:
        public async Task ShowTemperatures()
        {
            var sites = new[]
            {
                "http://www.site1.com/svc",
                "http://www.site2.com/svc",
                "http://www.site3.com/svc",
            };
            var tasksList = sites.Select(site => downloadTask(site)).ToList();

            while (tasksList.Any())
            {
                try
                {
                    var tempTask = await Task.WhenAny(tasksList);
                    tasksList.Remove(tempTask);

                    var result = await tempTask;
                    //todo: show result
                }
                catch(Exception ex) { }
            }
        }
در اینجا در یک حلقه، هر Taskایی که زودتر پایان یابد، نمایش داده شده و سپس از لیست وظایف حذف می‌شود. در ادامه مجددا یک await روی آن انجام خواهد شد تا استثنای احتمالی آن بروز کند. سپس اگر مشکلی نبود، می‌توان نتیجه را نمایش داد.

کاربرد سوم WhenAny کنترل تعداد وظایف همزمان است. برای مثال اگر قرار است هزاران تصویر از اینترنت دریافت شوند، نباید تمام وظایف را یکجا راه اندازی کرد. شاید نیاز باشد هربار فقط 15 وظیفه‌ی همزمان عمل کنند و نه بیشتر. در این حالت، مثال قبلی دارای یک حلقه‌ی کنترل کننده tasksList ارائه شده خواهد شد. هر بار تعداد معینی وظیفه به tasksList اضافه و پردازش می‌شوند و این روند تا پایان کار تعداد Urlها ادامه خواهد یافت (یک Take و Skip است؛ مانند صفحه بندی اطلاعات).


متدهای Run و FromResult

متد Task.Run اضافه شده در دات نت 4.5 به این معنا است که می‌خواهید Task ایجاد شده بر روی Thread pool اجرا شود. پارامتر آن می‌تواند یک delegate یا عبارت lambda و یا حتی یک Task باشد. خروجی آن نیز یک Task است و به همین جهت با async و await سی شارپ 5 سازگاری بهتری دارد.
استفاده از Task.Run نسبت به عملیات Threading متداول کارآیی بهتری دارد، زیرا ایجاد Threadهای جدید زمانبر بوده و زمانیکه به صورت خودکار از Thread pool استفاده می‌شود، تا حد امکان، استفاده‌ی مجدد از تردهای بیکار در حال حاضر، مدنظر است.

متد Task.FromResult کار بازگشت یک Task را از نتایج متدهای مختلف فراهم می‌کند. فرض کنید یک متد async تعریف کرده‌اید که خروجی آن Task of T است. در اینجا اگر داخل متد، از یک متد معمولی که یک عدد int را ارائه می‌دهد استفاده کنیم، با استفاده از Task.FromResult بلافاصله می‌توان یک Task of int را بازگشت داد.


متد Delay

پیشتر برای به خواب فرو بردن یک ترد از متد Thread.Sleep استفاده می‌شد. کار Thread.Sleep بلاک کردن ترد جاری است. در دات نت 4.5، بجای آن باید از Task.Delay استفاده شود که یک مکانیزم غیر قفل کننده را جهت صبر کردن به همراه بازگشت یک Task، ارائه می‌دهد.
یکی از کاربردهای Delay منهای صبر کردن تا مدت زمانی مشخص، ایجاد مکانیزم timeout است. برای مثال حالت Task.WhenAny را درنظر بگیرید. اگر در اینجا timeout مدنظر ما 3 ثانیه باشد، می‌توان یکی از Taskها را Task.Delay با آرگومان مساوی 3000 معرفی کرد. اگر هر کدام از taskهای تعریف شده زودتر از 3 ثانیه پایان یافتند که بسیار خوب؛ در غیر اینصورت Task.Delay معرفی شده کار را تمام می‌کند.


متد Yield
متد Task.Yield بسیار شبیه به متد قدیمی DoEvents است که از آن برای اجازه دادن به سایر اعمال جهت اجرا، در بین یک عمل طولانی، استفاده می‌شد.


متد ConfigureAwait

به صورت پیش فرض ادامه یک عملیات همزمان، بر روی ترد ایجاد کننده‌ی آن اجرا می‌شود. برای نمونه اگر یک عملیات async در ترد UI آغاز شود، نتیجه‌ی آن نیز در همان ترد UI بازگشت داده می‌شود. به این ترتیب دیگر نیازی نخواهد بود تا نگرانی در مورد نحوه‌ی دسترسی به مقدار آن توسط عناصر UI داشته باشیم.
اگر به این مساله اهمیت نمی‌دهید، برای مثال اگر اعمال در حال انجام، کاری به عناصر UI ندارند، از متد ConfigureAwait با پارامتر false بر روی یک task پیش از فراخوانی await بر روی آن، استفاده کنید.
 byte [] buffer = new byte[0x1000];
int numRead;
while((numRead = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{
  await source.WriteAsync(buffer, 0, numRead).ConfigureAwait(false);
}
این مثال در طی یک حلقه، هر بار مقدار کوچکی از منبع ارائه شده به آن را می‌خواند. در اینجا تعداد await cycles قابل توجهی وجود دارند. در هر سیکل نیز از دو فراخوانی async استفاده می‌شود؛ یکی برای انجام عملیات و دیگری برای بازگشت نتیجه به Synchronization Context آغاز کننده آن. با استفاده از ConfigureAwait false زمان اجرای این حلقه به شدت بهبود خواهد یافت و کوتاه‌تر خواهد شد؛ زیرا فاز هماهنگی آن با Synchronization Context حذف می‌شود.



به صورت خلاصه در سی شارپ 5

- بجای task.Wait قدیمی، از await task برای صبر کردن تا پایان یک task استفاده کنید.
- بجای task.Result جهت دریافت یک نتیجه‌ی یک task از await task کمک بگیرید.
- بجای Task.WaitAll از await Task.WhenAll و بجای Task.WaitAny از await Task.WhenAny استفاده نمائید.
- همچنین Thread.Sleep در اعمال async با await Task.Delay جایگزین شده‌است.
- در اعمال غیرهمزمان همیشه متد ConfigureAwait false را بکار بگیرید، مگر اینکه به Context نهایی آن واقعا نیاز داشته باشید.
و برای ایجاد یک Task جدید از Task.Run یا TaskFactory.StartNew استفاده نمائید.
مطالب
لینک‌های هفته دوم آذر

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


Visual Studio


امنیت

ASP. Net


طراحی وب


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


سی‌شارپ


عمومی دات نت


متفرقه


مطالب
حذف سریع تمام رکوردها در SQL server

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

--Disable Constraints & Triggers
exec sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
exec sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL'
--Perform delete operation on all table for cleanup
exec sp_MSforeachtable 'DELETE ?'
--Enable Constraints & Triggers again
exec sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
exec sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL'
--Reset Identity on tables with identity column
exec sp_MSforeachtable 'IF OBJECTPROPERTY(OBJECT_ID(''?''), ''TableHasIdentity'') = 1 BEGIN DBCC CHECKIDENT (''?'',RESEED,0) END'

اسکریپت فوق تمامی رکوردهای دیتابیس جاری را حذف کرده و همچنین فیلدهای identity را نیز مجددا به حالت اول باز می‌گرداند.

توضیحات:
sp_Msforeachtable یکی از رویه‌های ذخیره شده‌ی سیستمی اس کیوال سرور می‌باشد که مستند نشده است. اگر نیاز داشتید کدی را بدون نوشتن یک کرسر و امثال آن، بر روی تمامی جداول اجرا کنید می‌توان از آن استفاده نمود.
امضای این رویه ذخیره شده به صورت زیر است:

exec @RETURN_VALUE=sp_MSforeachtable @command1, @replacechar, @command2,
@command3, @whereand, @precommand, @postcommand

که در آن:
RETURN_VALUE مقدار بازگشتی است.
Command1 اولین دستوری است که اجرا خواهد شد (به همین ترتیب سپس Command2 و بعد از آن Command3 اجرا خواهد گردید)
Replacechar کاراکتری است که در دستور T-SQL مورد نظر جایگزین نام جدول خواهد شد. مقدار پیش فرض آن ? است.
Precommand پیش از پردازش عملیات روی هر جدولی اجرا می‌شود.
Postcommand پس از اجرای کلیه دستورات روی تمامی جداول، اجرا خواهد شد.

چند مثال:
نمایش تمامی جداول دیتابیس جاری
EXEC sp_MSforeachtable "print '?'"
نمایش اندازه‌ی جداول یک دیتابیس
EXEC sp_msforeachtable 'sp_spaceused ''?'''

مطالب
نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Popover به کمک Twitter bootstrap
این مطلب در ادامه بحث «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» می‌باشد. بنابراین تعاریف مدل و کنترلر آن، به همراه توضیحات ذکر شده در آن، در ادامه مورد استفاده قرار خواهند گرفت.


اصول نمایش Popover در Twitter bootstrap

PopOverها نیز یکی دیگر از کامپوننت‌های جاوا اسکریپتی مجموعه بوت استرپ هستند. بسیار شبیه به Tooltip بوده، اما ماندگارتر هستند. PopOverها با کلیک بر روی یک عنصر باز شده و تنها با کلیک مجدد بر روی آن المان، بسته می‌شوند (البته این موارد نیز قابل تنظیم هستند).
<a rel="popover" 
               data-content="محتوایی برای نمایش" 
               data-original-title="عنوان" href="#">اطلاعات</a>

    <script type="text/javascript">
        $(document).ready(function () {
            $("[rel='popover']").popover({ placement: 'left' })
                            .click(function (e) { e.preventDefault(); });
        });
   </script>
نحوه استفاده از آن را در مثال فوق مشاهده می‌کنید. در اینجا یک لینک با rel=popover تعریف شده است. از این rel، در یافتن کلیه المان‌هایی اینگونه، توسط jQuery استفاده خواهیم کرد. سپس مقدار ویژگی data-content، محتوای اطلاعاتی را که باید نمایش داده شود، مشخص می‌کند. همچنین برای مشخص ساختن عنوان آن می‌توان از ویژگی data-original-title استفاده کرد. نهایتا نیاز است افزونه popover بر روی المان‌هایی با rel=popover فراخوانی گردد. در روال رخدادگردان click آن، با استفاده از e.preventDefault، سبب خواهیم شد تا با کلیک بر روی لینک تعریف شده، صفحه مجددا بازیابی نشده و مکان اسکرول عمودی صفحه، تغییر نکند.


تبدیل خطاهای اعتبارسنجی ASP.NET MVC به PopOver

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

همانطور که ملاحظه می‌کنید، اینبار بجای نمایش خطاها در یک برچسب، مقابل کنترل متناظر، این خطا صرفا در حالت فوکوس کنترل، به شکل یک PopOver در کنار آن ظاهر شده است.


کدهای کامل View برنامه

@model Mvc4TwitterBootStrapTest.Models.User
@{
    ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset class="form-horizontal">
        <legend>تعریف کاربر جدید</legend>
        <div class="control-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label" })
            <div class="controls">
                @Html.EditorFor(model => model.Name)
                @*@Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })*@
            </div>
        </div>
        <div class="control-group">
            @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
            <div class="controls">
                @Html.EditorFor(model => model.LastName)
                @*@Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" })*@
            </div>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">
                ارسال</button>
        </div>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
        $.validator.setDefaults({
            showErrors: function (errorMap, errorList) {
                this.defaultShowErrors();
                //اگر المانی معتبر است نیاز به نمایش پاپ اور ندارد
                $("." + this.settings.validClass).popover("destroy");
                //افزودن پاپ اورها
                for (var i = 0; i < errorList.length; i++) {
                    var error = errorList[i];
                    $(error.element).popover({ placement: 'left' })
                                    .attr("data-original-title", "خطای اعتبارسنجی")
                                    .attr("data-content", error.message);
                }
            },
            // همانند قبل برای رنگی کردن کل ردیف در صورت عدم اعتبار سنجی و برعکس
            highlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                } else {
                    $(element).addClass(errorClass).removeClass(validClass);
                    $(element).closest('.control-group').removeClass('success').addClass('error');
                }
                $(element).trigger('highlited');
            },
            unhighlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                } else {
                    $(element).removeClass(errorClass).addClass(validClass);
                    $(element).closest('.control-group').removeClass('error').addClass('success');
                }
                $(element).trigger('unhighlited');
            }
        });
        //برای حالت پست بک از سرور عمل می‌کند
        $(function () {
            $('form').each(function () {
                $(this).find('div.control-group').each(function () {
                    if ($(this).find('span.field-validation-error').length > 0) {
                        $(this).addClass('error');
                    }
                });
            });
        });
   </script>
}
کدهای مدل و کنترلر، همانند مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» می‌باشند و از تکرار مجدد آن‌ها در اینجا صرفنظر گردید.

توضیحات
- با توجه به اینکه دیگر نمی‌خواهیم خطاها به صورت برچسب در مقابل کنترل‌ها نمایش داده شوند، کلیه Html.ValidationMessageFor به صورت کامنت درآورده شده‌اند.
- تغییر دوم مطلب جاری، اضافه شدن متد showErrors به تنظیمات پیش فرض jQuery Validator است. در این متد، اگر المانی معتبر بود، Popover آن حذف می‌شود یا در سایر حالات، المان‌هایی که نیاز به اعتبارسنجی سمت کلاینت دارند، یافت شده و سپس ویژگی data-content با مقداری معادل خطای اعتبارسنجی متناظر، به این المان افزوده و سپس متد popover بوت استرپ بر روی آن فراخوانی می‌گردد.
به عبارتی زمانیکه یک input box در ASP.NET MVC به همراه مقادیر مرتبط با اعتبارسنجی آن رندر می‌شود، چنین شکلی را خواهد داشت:
<input class="text-box single-line" data-val="true" data-val-required="لطفا نام را تکمیل کنید"
 id="Name" name="Name" type="text" value="" />
اما در اینجا به صورت پویا، data-original-title و data-content نیز به آن افزوده می‌گردند:
<input class="text-box single-line input-validation-error" data-val="true" data-val-required="لطفا نام را تکمیل کنید"
 id="Name" name="Name" type="text" value="" 
data-original-title="خطای اعتبارسنجی" title="" data-content="لطفا نام را تکمیل کنید">
این مقادیر توسط افزونه popover بوت استرپ شناسایی شده و مورد استفاده قرار می‌گیرد.
البته این موارد را در صورت نیاز به صورت دستی نیز می‌توان تعریف و اضافه کرد:
 @Html.TextBoxFor(x => x.Name, 
         new { data_content = "Name is required", 
               data_original_title = "Error", rel="popover" })
مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت چهارم - اعتبارسنجی Async سمت کلاینت و یا همان Remote Client Side Validation
در قسمت قبل با نحوه‌ی پیاده سازی اعتبارسنجی‌های سفارشی سمت کلاینت مخصوص کتابخانه‌ی Fluent Validation آشنا شدیم. در این قسمت، یک حالت خاص همان نوع اعتبارسنجی‌های سمت کلاینت را که remote validation نام دارد، بررسی می‌کنیم. در این حالت خاص، نیازی به کدنویسی جاوااسکریپتی خاصی نیست. چون زیرساخت آن به همراه unobtrusive jQuery Ajax خود ASP.NET Core ارائه می‌شود. در اینجا فقط نیاز است تا متادیتای خاص آن‌را تولید کنیم. به عبارتی اینبار هدف ما تنها تولید یک چنین تگ HTML ای است:
<input dir="ltr" class="form-control input-validation-error" 
type="email" 
data-val="true" 
data-val-email="'آدرس ایمیل' is not a valid email address." 
data-val-remote="این آدرس ایمیل هم اکنون مورد استفاده‌است" 
data-val-remote-url="/Home/ValidateUniqueEmail" 
data-val-required="'آدرس ایمیل' must not be empty." 
id="Email" 
name="Email" 
>
که به همراه ویژگی‌های data-val ، data-val-remote و data-val-remote-url است. همینقدر که این سه ویژگی وجود داشته باشند، مابقی منطق اعتبارسنجی سمت کلاینت آن توسط unobtrusive jQuery Ajax (ارسال خودکار Ajax ای مقدار ایمیل، به سمت سرور و دریافت پاسخ) و unobtrusive java script validation مدیریت خواهند شد.


افزودن آدرس ایمیل به مدل کاربران

به همان مدل قسمت قبل، قصد داریم خاصیت آدرس ایمیل را هم اضافه کنیم:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class UserModel
    {
        [Display(Name = "نام کاربری")]
        public string Username { get; set; }

        [Display(Name = "سن")]
        public int Age { get; set; }

        [Display(Name = "سابقه کار")]
        public int Experience { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        public string Email { get; set; }
    }
}


ایجاد سرویسی برای بررسی منحصربفرد بودن آدرس ایمیل

در ادامه قصد داریم سرویسی را ایجاد کنیم که برای مثال با بانک اطلاعاتی ارتباط برقرار کرده (در اینجا جهت سهولت ارائه، از یک آرایه استفاده شده‌است) و مشخص می‌کند که آیا ایمیل دریافتی پیشتر استفاده شده‌است یا خیر:
using System.Linq;

namespace FluentValidationSample.Services
{
    public interface IUsersService
    {
        bool IsUniqueEmail(string emailAddress);
    }

    public class UsersService : IUsersService
    {
        public bool IsUniqueEmail(string emailAddress)
        {
            string[] registedEmails = {
                "email@site.com",
                "test@gmail.com"
            };
            return !registedEmails.Contains(emailAddress);
        }
    }
}
این سرویس را هم با طول عمر Scoped به برنامه معرفی می‌کنیم؛ چون طول عمر سرویس‌هایی که با بانک اطلاعاتی و DbContext کار می‌کنند، به همین نحو تعیین می‌شوند:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUsersService, UsersService>();


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

در ادامه یک PropertyValidator جدید را ایجاد می‌کنیم تا بتوان توسط آن مقدار ایمیل دریافتی را در سمت سرور، تعیین اعتبار کرد:
using FluentValidation.Validators;
using FluentValidationSample.Services;

namespace FluentValidationSample.ModelsValidations
{
    public class UniqueEmailValidator : PropertyValidator
    {
        private readonly IUsersService _usersService;

        public UniqueEmailValidator(IUsersService usersService)
                : base("این آدرس ایمیل هم اکنون مورد استفاده‌است")
        {
            _usersService = usersService;
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            return context.PropertyValue != null &&
                    _usersService.IsUniqueEmail((string)context.PropertyValue);
        }
    }
}
همانطور که مشاهده می‌کنید، در اینجا تزریق سرویس سفارشی IUsersService به سازنده‌ی کلاس اعتبارسنج، مجاز است و بدون مشکل کار می‌کند.
پس از آن جهت سهولت استفاده‌ی از آن، یک متد الحاقی جدید را نیز به نام UniqueEmail به نحو زیر تعریف می‌کنیم:
using FluentValidation;
using FluentValidationSample.Services;

namespace FluentValidationSample.ModelsValidations
{
    public static class CustomValidatorExtensions
    {
        public static IRuleBuilderOptions<T, string> UniqueEmail<T>(
            this IRuleBuilder<T, string> ruleBuilder, IUsersService usersService)
        {
            return ruleBuilder.SetValidator(new UniqueEmailValidator(usersService));
        }
    }
}

یک نکته: اگر دقت کرده باشید، فضای نام این اعتبارسنج در این قسمت FluentValidationSample.ModelsValidations شده‌است:


علت اینجا است که اعتبارسنج تعریف شده نیاز دارد هم از مدل‌ها استفاده کند و هم از سرویس کاربران. سرویس کاربران هم از مدل‌ها استفاده می‌کند. به همین جهت اگر تعاریف اعتبارسنجی را داخل پروژه‌ی مدل‌ها قرار دهیم، یک وابستگی حلقوی رخ خواهد داد (وابستگی مدل‌ها به سرویس‌ها و برعکس). بنابراین بهتر است اعتبارسنج‌ها را به یک پروژه‌ی مجزا منتقل کنیم تا از بروز این cyclic dependency جلوگیری شود.


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

پس از تهیه‌ی متد الحاقی UniqueEmail، آن‌را به RuleFor مخصوص خاصیت ایمیل اضافه می‌کنیم. در اینجا نیز تزریق وابستگی سرویس سفارشی IUsersService به سازنده‌ی کلاس اعتبارسنج مجاز است:
using FluentValidation;
using FluentValidationSample.Models;
using FluentValidationSample.Services;

namespace FluentValidationSample.ModelsValidations
{
    public class UserModelValidator : AbstractValidator<UserModel>
    {
        public UserModelValidator(IUsersService usersService)
        {
            RuleFor(x => x.Username).NotNull();
            RuleFor(x => x.Age).NotNull();
            RuleFor(x => x.Experience).LowerThan(nameof(UserModel.Age)).NotNull();
            RuleFor(x => x.Email).EmailAddress().NotNull().UniqueEmail(usersService);
        }
    }
}


ایجاد متادیتای مورد نیاز جهت unobtrusive java script validation در سمت سرور

در ادامه نیاز است ویژگی‌های data-val خاص unobtrusive java script validation را توسط FluentValidation ایجاد کنیم:
using FluentValidation;
using FluentValidation.AspNetCore;
using FluentValidation.Internal;
using FluentValidation.Resources;
using FluentValidation.Validators;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace FluentValidationSample.ModelsValidations
{
    public class RemoteClientValidator : ClientValidatorBase
    {
        public string RemoteUrl { set; get; }

        public RemoteClientValidator(PropertyRule rule, IPropertyValidator validator) :
            base(rule, validator)
        {
        }

        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-remote", GetErrorMessage(context));
            MergeAttribute(context.Attributes, "data-val-remote-url", RemoteUrl);
        }

        private string GetErrorMessage(ClientModelValidationContext context)
        {
            var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName());
            string messageTemplate;
            try
            {
                messageTemplate = Validator.Options.ErrorMessageSource.GetString(null);
            }
            catch (FluentValidationMessageFormatException)
            {
                messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>();
            }
            return formatter.BuildMessage(messageTemplate);
        }
    }
}
در این کدها، تنها قسمت مهم آن، متد AddValidation است که کار تعریف و افزودن متادیتاهای unobtrusive java script validation را انجام می‌دهد و برای مثال سبب رندر تگ HTML ای زیر می‌شود:
<input dir="ltr" class="form-control input-validation-error" 
type="email" 
data-val="true" 
data-val-email="'آدرس ایمیل' is not a valid email address." 
data-val-remote="این آدرس ایمیل هم اکنون مورد استفاده‌است" 
data-val-remote-url="/Home/ValidateUniqueEmail" 
data-val-required="'آدرس ایمیل' must not be empty." 
id="Email" 
name="Email" 
>
که به همراه ویژگی‌های data-val، data-val-remote و data-val-remote-url است تا unobtrusive jQuery Ajax validation را فعال کند.


افزودن اعتبارسنج‌های تعریف شده به تنظیمات برنامه

پس از تعریف UniqueEmailValidator و RemoteClientValidator، روش افزودن آن‌ها به تنظیمات FluentValidation به صورت زیر است:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUsersService, UsersService>();

            services.AddControllersWithViews().AddFluentValidation(
                fv =>
                {
                    fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly());
                    fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>();
                    fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;

                    fv.ConfigureClientsideValidation(clientSideValidation =>
                    {
                        // ...

                        clientSideValidation.Add(
                            validatorType: typeof(UniqueEmailValidator),
                            factory: (context, rule, validator) =>
                                        new RemoteClientValidator(rule, validator)
                                        {
                                            RemoteUrl = "/Home/ValidateUniqueEmail"
                                        });
                    });
                }
            );
        }
در اینجا یک RemoteUrl را هم مشاهده می‌کنید که به صورت زیر باید تعریف شود:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IUsersService _usersService;

        public HomeController(IUsersService usersService)
        {
            _usersService = usersService;
        }

         // ...

        public IActionResult ValidateUniqueEmail(string email)
        {
            return Ok(_usersService.IsUniqueEmail(email));
        }
    }
}
زمانیکه اعتبارسنجی سمت کلاینت رخ می‌دهد، آدرس ایمیل، به اکشن متد فوق ارسال شده و یک true و یا false را دریافت می‌کند که بیانگر موفقیت آمیز بودن و یا شکست اعتبارسنجی از راه دور است.


تعریف کدهای جاوا اسکریپتی مورد نیاز

پیش از هرکاری، اسکریپت‌های فایل layout برنامه باید چنین تعریفی را داشته باشند:
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
در اینجا مداخل jquery، سپس jquery.validate و بعد از آن jquery.validate.unobtrusive را مشاهده می‌کنید. در ادامه نیازی به تکمیل فایل js/site.js جهت افزودن کدهای remote client validation نیست و این کدها جزئی از کتابخانه‌ی jquery.validate.unobtrusive هستند.


آزمایش برنامه

View این قسمت نیز همانند قسمت قبل است که فقط یک آدرس ایمیل به آن اضافه شده‌است:


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


کدهای کامل این سری را تا این قسمت از اینجا می‌توانید دریافت کنید: FluentValidationSample-part04.zip
مطالب
استفاده از پروایدر SQLite در Entity Framework 7
Entity Framework در نگارش 7 خود از منابع داده‌ایی جدیدی پشتیبانی میکند(+) . یعنی از Windows Phone، Windows Store و همچنین ASP.NET 5 (اپلیکیشن‌هایی که از NET Core. استفاده می‌کنند) پشتیبانی خواهد کرد. در این نسخه از دیتابیس‌های non-relational نیز پشتیبانی می‌شود. پروایدر SQLite به صورت رسمی توسط تیم EF ارائه شده است که در ادامه نحوه‌ی استفاده از آن را در یک برنامه کنسول ساده بررسی خواهیم کرد.
کلاس‌های برنامه:
using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Metadata;
using System.Collections.Generic;
using System.Linq;

namespace UsingEF7WithSQLite
{
    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }

        public List<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }
    }
}
خب تا اینجا مدل‌های برنامه را تعریف کردیم، قدم بعدی افزودن پکیج مربوط به پروایدر SQLite به پروژه است، با دستور زیر این پکیج را نصب می‌کنیم:
PM> Install-Package EntityFramework.SQLite –Pre
اکنون کلاس کانتکست برنامه را به صورت زیر تعریف می‌کنیم:
namespace UsingEF7SQLiteProvider
{
    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnConfiguring(DbContextOptions builder)
        {
            builder.UseSQLite(@"Data Source=.\BloggingDatabae.db");
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<Blog>()
                .OneToMany(b => b.Posts, p => p.Blog)
                .ForeignKey(p => p.BlogId);

            // The EF7 SQLite provider currently doesn't support generated values
            // so setting the keys to be generated from developer code
            builder.Entity<Blog>()
                .Property(b => b.BlogId)
                .GenerateValueOnAdd(false);

            builder.Entity<Post>()
                .Property(b => b.BlogId)
                .GenerateValueOnAdd(false);
        }
    }
}

کار را با بازنویسی متد OnConfiguration شروع می‌کنیم، در این قسمت باید به EF بگوئیم که می‌خواهیم از SQLite استفاده کنیم برای اینکار از یک Extension Method با نام UseSQLite و پاس دادن کانکشتن استرینگ به آن استفاده می‌کنیم.
نکته: پروایدر فعلی SQLite در حال حاضر از Generated values پشتیبانی نمی‌کند، برای این منظور باید درون متد OnModelCreating این قابلیت را غیرفعال کنیم.
اکنون می‌توانیم از طریق پاورشل نیوگت دیتابیس را ایجاد کنیم، برای اینکار باید پکیج زیر را به پروژه اضافه کنید:
Install-Package EntityFramework.Commands -Pre
سپس دستورات زیر را اجرا می‌کنیم:
Add-Migration MyFirstMigration
Apply-Migration
توسط دستور Apply-Migrate دیتابیس برای شما ایجاد خواهد شد. البته این دستور زمانی استفاده می‌شود که برنامه شما یک اپلیکیشن دسکتاپ باشد. اگر اپلیکیشن شما یک Windows Phone Application است باید در زمان اجرای برنامه این کد را بنویسید:
using (var db = new BloggingContext())
{
   db.Database.AsMigrationsEnabled().ApplyMigrations();
}
در نهایت می‌توانید با دستور زیر از کانتکست برنامه استفاده کرده و خروجی را مشاهده کنید:
using (var db = new Models.BloggingContext())
{
     db.Blogs.Add(new Models.Blog { Url = "https://www.dntips.ir" });
     db.SaveChanges();

     foreach (var item in db.Blogs)
     {
         Console.WriteLine(item.Url);
     }
}
Console.ReadLine();