نظرات مطالب
معرفی ASP.NET Identity
این پروژه سورس باز هست. مشکلات آن‌را برای رفع در اینجا گزارش کنید. نحوه‌ی گزارش مشکل هم باید کمی فنی باشد. حداقل جزئیات exception و stack trace آن باید گزارش شوند.
نظرات مطالب
SignalR - قسمت سوم
با سلام و خسته نباشید
واقعا ممنون از مطالبی که گذاشتین. خیلی از مشکلات من رو حل کرد. خواهش می‌کنم بحث رو ادامه بدید.
با تشکر
نظرات مطالب
Eazfuscator 2.6 منتشر شد
سلام، ابزار کوچک جالب و مفیدی بود. در مورد مشکلات احتمالی اسمبلی های مربوط به asp.net ، آیا تا کنون از Remotesoft Salamander استفاده کرده‌اید،... مشکلی داشته است؟؟
پاسخ به بازخورد‌های پروژه‌ها
انتقال Issue ها به گیت هاب
هر طور که صلاح می‌دانید یا راحت‌تر هستید.
در کل این مشکلات به سورس اصلی در گیت‌هاب اعمال شده و نتیجه‌ی نهایی در آنجا قرار می‌گیرند.
پاسخ به بازخورد‌های پروژه‌ها
فونت نستعلیق
خوب در این توضیح داده و به نظر گفته بعضی از مشکلات رو رفع کرده
اما من نفهمیدم دقیقا چه کاری کرده
آیا گزارش گیر دیگه ای هست که شما بدونید و مشکل با فونت نستعلیق نداشته باشه؟
نظرسنجی‌ها
Sql vs noSql vs Both کدام انتخاب بهتر است؟
استفاده از RDBMS/Sql و هندل مشکلات Big Data با آن!
استفاده از NoSql و پیاده سازی بخش های Relational در آن
استفاده همزمان از هردو دیتابیس در یک پروژه
مطالب
Minimal API's در دات نت 6 - قسمت پنجم - پیاده سازی الگوی CQRS
تا قسمت قبل موفق شدیم فایل Program.cs برنامه‌ی Minimal API's را خلوت کنیم و همچنین زیرساختی را برای توسعه‌ی مبتنی بر ویژگی‌ها، ارائه دهیم. اما ... هنوز endpoints ما چنین شکلی را دارند:
        endpoints.MapGet("/api/authors", async (MinimalBlogDbContext ctx) =>
        {
            var authors = await ctx.Authors.ToListAsync();
            return authors;
        });

        endpoints.MapPost("/api/authors", async (MinimalBlogDbContext ctx, AuthorDto authorDto) =>
        {
            var author = new Author();
            author.FirstName = authorDto.FirstName;
            author.LastName = authorDto.LastName;
            author.Bio = authorDto.Bio;
            author.DateOfBirth = authorDto.DateOfBirth;

            ctx.Authors.Add(author);
            await ctx.SaveChangesAsync();

            return author;
        });
 و یک چنین رویه‌ای جهت کار مستقیم با DbContext در اکشن متدهای MVC هیچگاه توصیه نمی‌شود. برای مثال به طور معمول، عملیاتی که در بدنه‌ی Lambda expressions فوق انجام شده، عموما به Repositories و Services محول شده و در نهایت از سرویس‌ها، در اکشن متدها استفاده می‌شود. در معماری جاری که در پیش گرفته‌ایم، دو لایه‌ی Repositories و Services حذف شده‌اند و دیگر خبری از آن‌ها نیست. در اینجا کار سرویس‌ها و مخازن، به هندلرهای معماری CQRS واگذار خواهند شد. هر هندلر نیز متکی به خود است و مستقل از سایر هندلرها طراحی می‌شود و این‌ها صرفا بر اساس نیازهای ویژگی جاری توسعه خواهند یافت و دقیقا در همان پوشه‌ی ویژگی مورد بررسی نیز قرار می‌گیرند؛ و نه پراکنده در لایه‌ای و یا پروژه‌ای دیگر. به این ترتیب درک یک ویژگی متکی به خود برنامه، ساده‌تر شده و در طول زمان، نگهداری و توسعه‌ی آن نیز ساده‌تر خواهد شد. مشکل داشتن سرویس‌هایی بزرگ که در معماری‌های متداول وجود دارند، استفاده‌ی از متدهای آن‌ها در چندین اکشن متد و چندین کنترلر مختلف است و اگر یکی از متدهای این سرویس بزرگ ما تغییر کند، بر روی چندین کنترلر تاثیر می‌گذارد که ممکن است سبب از کار افتادگی بعضی از آن‌ها شود؛ اما در اینجا هرکاری که انجام می‌شود و هر هندلری که توسعه می‌یابد، فقط مختص به یک کار و یک ویژگی مشخص است.


ایجاد Command و هندلر مخصوص ایجاد یک نویسنده‌ی جدید


در الگوی CQRS، یک دستور، کاری را بر روی بانک اطلاعاتی انجام می‌دهد. برای مثال در اینجا قرار است نویسنده‌ای را ثبت کند. در ادامه می‌خواهیم بدنه‌ی endpoints.MapPost فوق را با الگوی CQRS انطباق دهیم. به همین جهت به یک Command نیاز داریم:
using MediatR;
using MinimalBlog.Domain.Model;

namespace MinimalBlog.Api.Features.Authors;

public class CreateAuthorCommand : IRequest<Author>
{
    public AuthorDto AuthorDto { get; set; } = default!;
}
اینترفیس IRequest کتابخانه‌ی MediatR که در انتهای قسمت قبل به پروژه اضافه شد، چنین امضایی را دارد:
public interface IRequest<out TResponse> : IBaseRequest
یعنی <IRequest<Author به این معنا است که قرار است «خروجی» این عملیات، یک Author باشد و CreateAuthorCommand می‌تواند شامل تمام خواصی باشد که در جهت برآورده کردن این دستور مورد نیاز هستند؛ برای مثال کل اطلاعات شیء AuthorDto در اینجا.

سپس نیاز به یک هندلر است تا دستور رسیده را پردازش کند:
namespace MinimalBlog.Api.Features.Authors;

public class CreateAuthorCommandHandler : IRequestHandler<CreateAuthorCommand, Author>
{
    private readonly MinimalBlogDbContext _context;
    private readonly IMapper _mapper;

    public CreateAuthorCommandHandler(MinimalBlogDbContext context, IMapper mapper)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
    }

    public async Task<Author> Handle(CreateAuthorCommand request, CancellationToken cancellationToken)
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        var toAdd = _mapper.Map<Author>(request.AuthorDto);
        _context.Authors.Add(toAdd);
        await _context.SaveChangesAsync(cancellationToken);
        return toAdd;
    }
}
اینترفیس IRequestHandler چنین امضایی را دارد:
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse>
که اولین آرگومان جنریک آن، همان Command ای است که قرار است پردازش کند و خروجی آن، اطلاعاتی است که قرار است بازگشت دهد. یعنی متد Handle فوق، قرار است عملیات endpoints.MapPost را پیاده سازی کند و در اینجا با استفاده از AutoMapper، انتساب‌های آن حذف و ساده شده‌اند و مابقی آن، با بدنه‌ی lambda expression مربوط به endpoints.MapPost، یکی است. این هندلر، معادل یک یا چند متد از متدهای یک سرویس بزرگ است که در اینجا به صورت اختصاصی جهت پردازش فرمانی در کنار هم قرار می‌گیرند و متکی به خود هستند.

پس از این تغییرات، بدنه‌ی lambda expression مربوط به endpoints.MapPost به صورت زیر تغییر کرده و ساده می‌شود:
endpoints.MapPost("/api/authors", async (IMediator mediator, AuthorDto authorDto) =>
{
     var command = new CreateAuthorCommand { AuthorDto = authorDto };
     var author = await mediator.Send(command);
     return author;
});
در اینجا تزریق وابستگی IMediator را مشاهده می‌کنید. با فراخوانی متد Send آن، شیء‌ای به هندلر متناظری ارسال شده، پردازش می‌شود و در نهایت شیءای را بازگشت خواهد داد. برای مثال در اینجا شیء Dto یک نویسنده به هندلر CreateAuthorCommandHandler ارسال و تبدیل به شیءای از نوع Author مربوط به دومین برنامه شده، سپس در بانک اطلاعاتی ذخیره می‌شود و در نهایت این نویسنده که اکنون به همراه یک Id نیز هست، بازگشت داده می‌شود. بنابراین هر هندلر یک object in و یک object out دارد که به عنوان آرگومان‌های جنریک IRequestHandler تعریف می‌شوند.



نکته 1: await داخل بدنه‌ی lambda expression مربوط به endpoints را فراموش نکنید. تمام متدهای IMediator از نوع aysnc هستند؛ هرچند روش نامگذاری SendAsync را رعایت نکرده‌اند و اگر این await فراموش شود، مشاهده خواهید کرد که برنامه در حین فراخوانی endpoints در مرورگر، در حالت هنگ و صبر کردن نامحدود قرار می‌گیرد، بدون اینکه کاری را انجام دهد و یا حتی استثنایی را صادر کند.


نکته 2: در پیاده سازی هندلر، استفاده از cancellationToken را نیز مشاهده می‌کنید. تقریبا تمام متدهای async مربوط به EF-Core به همراه پارامتری جهت دریافت cancellationToken هم هستند. اگر کاربری قصد لغو یک درخواست طولانی را داشته باشد و بر روی دکمه‌ی stop مرورگر کلیک کند و یا حتی صفحه را چندین بار ریفرش کند، این به معنای abort درخواست(های) رسیده‌است. وجود این cancellationTokenها، بار سرور را کاهش داده و عملیات در حال اجرای سمت سرور را در یک چنین حالت‌هایی متوقف می‌کند.
البته هندلری که در اینجا تعریف شده، این cancellationToken را باید از mediator دریافت کند که در کدهای endpoint فوق، چنین نیست. برای رفع این مشکل باید به صورت زیر عمل کرد:
endpoints.MapGet("/api/authors", async (IMediator mediator, CancellationToken ct) =>
        {
            var request = new GetAllAuthorsQuery();
            var authors = await mediator.Send(request, ct);
            return authors;
        });
این مورد را می‌توان به صورت یک best practice، به تمام endpoints اضافه کرد.


نکته 3: هندلرها عموما چیزی را بازگشت نمی‌دهند؛ صرف نظر از هندلر فوق که نیاز بوده تا Id شیء ذخیره شده را بازگشت دهد، عموما به همراه هیچ خروجی نیستند. به همین جهت در حین تعریف آن‌ها فقط کافی است در آرگومان‌های جنریک آن‌ها، نوع خروجی را ذکر نکنیم:
public class Handler : IRequestHandler<Command>
در یک چنین حالتی، امضای IRequestHandler به صورت خودکار به همراه خروجی از نوع Unit خواهد بود:
public interface IRequestHandler<in TRequest> : IRequestHandler<TRequest, Unit> where TRequest : IRequest<Unit>
که این Unit معادل Void در کتابخانه‌ی mediator است و به نحو زیر در هندلرها مدیریت می‌شود:
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
   // ...
   return Unit.Value;
}
در یک چنین حالتی، تعریف یک Command نیز بر اساس اینترفیس IRequest انجام می‌شود:
public class Command : IRequest


ایجاد Query و هندلر مخصوص بازگشت لیست نویسنده‌‌ها

در الگوی CQRS، یک کوئری قرار است اطلاعاتی را بازگشت دهد و ... وضعیت بانک اطلاعاتی را تغییر نمی‌دهد. بنابراین در اینجا یک IRequest که قرار است لیستی از نویسندگان را بازگشت دهد، تعریف می‌کنیم. بدنه‌ی آن هم می‌تواند خالی باشد و یا به همراه خواصی مانند اطلاعات صفحه بندی و یا مرتب سازی گزارشگیری رسیده‌ی از درخواست:
using MediatR;
using MinimalBlog.Domain.Model;

namespace MinimalBlog.Api.Features.Authors;

public class GetAllAuthorsQuery : IRequest<List<Author>>
{
}
سپس نیاز به یک هندلر است تا درخواست رسیده را پردازش کند. این هندلر، کوئری فوق را دریافت کرده و لیست کاربران را بازگشت می‌دهد:
namespace MinimalBlog.Api.Features.Authors;

public class GetAllAuthorsHandler : IRequestHandler<GetAllAuthorsQuery, List<Author>>
{
    private readonly MinimalBlogDbContext _context;

    public GetAllAuthorsHandler(MinimalBlogDbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public Task<List<Author>> Handle(GetAllAuthorsQuery request, CancellationToken cancellationToken)
    {
        return _context.Authors.ToListAsync(cancellationToken);
    }
}
پس از این تغییرات، بدنه‌ی lambda expression مربوط به endpoints.MapGet به صورت زیر تغییر کرده و ساده می‌شود:
endpoints.MapGet("/api/authors", async (IMediator mediator) =>
{
   var request = new GetAllAuthorsQuery();
   var authors = await mediator.Send(request);
   return authors;
});
مزیت استفاده‌ی از الگوی CQRS، تنها به حذف لایه‌ی سرویس و رسیدن به ویژگی‌هایی مستقل و متکی به خود، منحصر نیست. با استفاده از این الگو می‌توان مقیاس پذیری برنامه را نیز افزایش داد. برای مثال یک بانک اطلاعاتی بهینه سازی شده را صرفا برای کوئری‌ها، درنظر گرفت و بانک اطلاعاتی دیگری را تنها برای اعمال Write که Commands بر روی آن اجرا می‌شوند و در اینجا تنها نیاز به همگام سازی اطلاعات بانک اطلاعاتی Write، با بانک اطلاعاتی Read است که در بسیاری از اوقات پرکارتر از بانک‌های اطلاعاتی دیگر است:


و یا حتی معماری CQRS با معماری Event store نیز قابل ترکیب است:


در اینجا بجای استفاده از بانک اطلاعاتی Write، از یک Event store استفاده می‌شود. کار event store، دریافت رویدادهای write است و سپس باز پخش آن‌ها به بانک اطلاعاتی Read؛ تا کار همگام سازی به این نحو صورت گیرد.


روشی برای نظم دادن به نحوه‌ی تعریف کلاس‌های الگوی CQRS

تا اینجا برای مثال کلاسCreateAuthorCommand  را در یک فایل مجزا و سپس هندلر آن‌را به نام CreateAuthorCommandHandler در یک فایل دیگر تعریف کردیم. می‌توان جهت بالابردن خوانایی برنامه، کاهش رفت و برگشت‌ها برای یافتن کلاس‌های مرتبط و همچنین سهولت یافتن هندلرهای مرتبط با هر متد mediator.Send، از روش زیر نیز استفاده کرد:
public static class CreateAuthor
{
    public class Command : IRequest<AuthorGetDto>
    {
        // ...
    }

    public class Handler : IRequestHandler<Command, AuthorGetDto>
    {
       // ...
    }
}
در اینجا از nested classes استفاده شده‌است. ابتدا نام اصلی Command و یا کوئری ذکر می‌شود؛ که نام کلاس دربرگیرنده‌ی اصلی را تشکیل می‌دهد. سپس دو کلاس بعدی فقط Command و Handler نام می‌گیرند و نه هیچ نام دیگری. به این ترتیب به یکسری نام یک دست در کل پروژه خواهیم رسید. زمانیکه قرار است mediator.Send فراخوانی شود، اینبار چنین شکلی را پیدا می‌کند که مزیت آن، سهولت یافتن هندلر مرتبط، فقط با پیگیری کلاس اصلی CreateAuthor است:
var command = new CreateAuthor.Command { AuthorDto = authorDto };
var author = await mediator.Send(command, ct);

در مورد کوئری‌ها هم می‌توان به قالب مشابهی رسید که در اینجا هم کوئری و هندلر آن، ذیل نام اصلی مدنظر قرار می‌گیرند:
public static class GetAllAuthors
{
    public class Query : IRequest<List<AuthorGetDto>>
    {
       //...
    }

    public class Handler : IRequestHandler<Query, List<AuthorGetDto>>
    {
       //...
    }
}
و اگر کدهای نهایی این سری را که از قسمت اول قابل دریافت است بررسی کنید، از همین ساختار یکدست، برای تعاریف دستورات و کوئری‌ها استفاده شده‌است.
مطالب
سیستم‌های توزیع شده در NET. - بخش هشتم- راه اندازی Apache Kafka
در این بخش نحوه راه اندازی Apache Kafka را در Ubuntu مورد بررسی قرار می‌دهیم و می‌بینیم که هر یک از تعاریف و اصطلاحاتی که در بخش قبل معرفی شد، چگونه پیاده سازی می‌شوند (در صورتی که امکان استفاده از Ubuntu را ندارید و می‌خواهید مراحل این بخش را در windows انجام دهید می‌توانید از راهنمای راه اندازی Kafka در windows استفاده کنید تا بتوانید بخشهای بعدی این سری مقالات را پیگیری و اجرا کنید).

بروز رسانی Ubuntu:
قبل از هرچیزی باید مطمئن شوید که Ubuntu بصورت کامل بروز رسانی شده است. برای این کار یک ترمینال جدید را باز و دستورهای زیر را در آن وارد کنید:
sudo apt-get update -y
sudo apt-get upgrade -y
نصب java:
نکته:درصورتی که قبلا java را نصب کرده‌اید نیازی به انجام این مرحله نیست و برای اطمینان از نصب java می‌توانید دستور زیر را در خط فرمان terminal ایجاد شده وارد کنید:
sudo java -version
قبل از دانلود و نصب Apache Kafka، باید ابتدا java را نصب کنید. برای این کار دستور زیر را اجرا کنید:
sudo add-apt-repository -y ppa:webupd8team/java
باید خروجی زیر به شما نمایش داده شود:
gpg: keyring `/tmp/tmpkjrm4mnm/secring.gpg' created
gpg: keyring `/tmp/tmpkjrm4mnm/pubring.gpg' created
gpg: requesting key EEA14886 from hkp server keyserver.ubuntu.com
gpg: /tmp/tmpkjrm4mnm/trustdb.gpg: trustdb created
gpg: key EEA14886: public key "Launchpad VLC" imported
gpg: no ultimately trusted keys found
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
OK
برای بروز رسانی metadata‌های repository جدید باید دستور زیر را وارد کنید:
sudo apt-get update
و در پایان دستور زیر را برای نصب java اجرا کنید:
sudo apt-get install oracle-java8-installer -y
برای اطمینان از نصب java دستور زیر را وارد کنید:
sudo java -version
در صورتیکه خروجی زیر به شما نمایش داده شود به این معنا است که عملیات نصب java با موفقیت انجام شده‌است:
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)

                                            
دانلود Apache Kafka :
آخرین نگارش پایدار از Apache Kafka، نگارش 1.0.0 می‌باشد که می‌توانید آن را از طریق صفحه دریافت Apache Kafka ، دریافت کنید.

دانلود Apache Kafka


پس از دریافت Apache Kafka، از طریق یک terminal جدید وارد پوشه Downloads شوید. سپس فایل مورد نظر را از حالت فشرده خارج کنید و وارد پوشه اصلی آن شوید. برای این کار از دستورات زیر استفاده کنید:

tar -xzf kafka_2.11-1.0.0.tgz
cd kafka_2.11-1.0.0

 سپس با استفاده از دستور "ls " لیست تمامی فایلها و فولدر‌های موجود در فولدر kafka_2.11-1.0.0  و   را نمایش دهید:

در لیست نمایش داده شده دو فولد اصلی bin و config وجود دارند که به ترتیب فایلهای اجرایی و کانفیگهای پیشفرض و مورد نیاز، در آنها قرار دارند.


اجرای Apache ZooKeeper:

همانطور که در بخش قبل توضیح داده شد، Apache Kafka هیچ Stateی را در خود ذخیره و مدیریت نمی‌کند و اصطلاحا Stateless می‌باشد و مدیریت تمامی این Stateها را بر عهده  Apache ZooKeeper قرار می‌دهد. بنابراین قبل از اینکه بخواهیم Kafka را اجرا کنیم، ابتدا باید Apache ZooKeeper را اجرا کنیم. برای اجرای Apache ZooKeeper در terminalی که قبلا باز کرده‌اید، دستور زیر را اجرا کنید:

bin/zookeeper-server-start.sh config/zookeeper.properties

با اجرای فایل zookeeper-server-start.sh و ارسال کانفیگ پیشفرضش در فولدر config با نام  zookeeper.properties، قسمت مدیریت کننده Stateهای Kafka  اجرا می‌شود.


اجرای Apache Kafka:

پس از اجرای Apache ZooKeeper، باید یک terminal جدید را باز کنید و با استفاده از دستور زیر kafka-server را با استفاده از کانفیگ پیشفرضش اجرا کنید:

 bin/kafka-server-start.sh config/server.properties


به همین راحتی Kafka Server اجرا شده و آماده‌ی استفاده است. برای ادامه و نمایش دادن سایر قابلیت‌های Kafka باید یک Topic جدید را ایجاد کنیم.


ایجاد Topic:

همانطور که می‌دانید تمامی پیامهای شما در Partition‌های Topic ذخیره می‌شوند؛ پس قبل از اینکه بخواهیم توسط یک Producer پیامی را ارسال کنیم یا اینکه بخواهیم توسط Consumer پیامی را دریافت کنیم، ابتدا باید Topic مربوطه را ایجاد کنیم. بهترین و عمومی‌ترین مثال برای نمایش قابلیت‌های Publish-Subscribe در Kafka، مثال چت بین کاربران است که در آن یک کاربر به عنوان Producer عمل می‌کند و پیامهایی را برای سایر کاربران که نقش Consumer را دارند ارسال می‌کند. kafka-topics .sh در Kafka ابزاریست برای مدیریت Topic ها  که با استفاده از آن می‌توانید Topic‌های مورد نیاز را تعریف، ویرایش و یا حذف کنید.

یک terminal جدید را آغاز و با استفاده از دستور زیر یک Topic را با نام userChat ایجاد کنید:

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic userChat

با این دستور یک Topic در Kafka با نام userChat ایجاد می‌شود و مشخصات آن (منظور stateهای مرتبط با آن است)، در zookeeper نیز ثبت می‌شود.


توضیحات Optionها و  آرگومانهایی که در دستور ایجاد Topic استفاده شدند به شرح زیر است:

create-- :

مشخص کننده این است که شما می‌خواهید یک Topic جدید را ایجاد کنید.

zookeeper localhost:2181--:

مشخص کننده آدرس سرور zookeeper است (سرور zookeeper بصورت پیشفرض از پورت 2181 استفاده می‌کند).

replication-factor 1--:

مشخص کننده این است که تنها یک کپی از این Topic در سرور ایجاد شود. البته در این مثال به دلیل اینکه تنها یک سرور از Kafka را اجرا کرده‌ایم در صورتیکه این مقدار را بیش از عدد 1 در نظر بگیریم، با خطا مواجه می‌شویم. 

partitions 1--

تعداد Partition‌های این Topic را مشخص می‌کند. مقدار Partition در حالتیکه حتی یک سرور را نیز اجرا کرده‌ایم، می‌تواند بیش از 1 باشد؛ اما در این حالت Primary Broker برای تمام Partition‌های همین سرور در نظر گرفته می‌شود. 

topic userChat--

نام Topic را مشخص می‌کند.


پس از اینکه Topic مورد نظر ایجاد شد، با استفاده از دستور زیر می‌توانیم لیست Topic‌های ایجاد شده را مشاهده کنیم:

 bin/kafka-topics.sh --list --zookeeper localhost:2181


توضیحات Optionها و آرگومانهایی که در دستور نمایش لیست  Topicها استفاده شدند به شرح زیر است: 

list--:

شما می‌خواهید لیست topic‌ها را مشاهده کنید.

zookeeper localhost:2181--:

همانند دستور ایجاد، مشخص کننده سرور zookeeper می‌باشد.


تا اینجا برای ارسال و دریافت پیام در بین Application‌های مختلف همه چیز آماده‌است. البته همانطور که در بخش‌های بعدی این سری مقالات می‌بینیم، در عمل Producer‌ها و Consumer‌ها زیرسیستمهایی هستند که خود ما با استفاده از هر زبان برنامه نویسی پیاده سازی می‌کنیم. اما در این قسمت برای نمایش و تکمیل مثالمان، از ابزارهایی که خود Kafka در اختیار ما قرار داده استفاده می‌کنیم.


اجرای یک Producer و ارسال پیام به Kafka:

kafka-console-producer.sh ابزاریست که با استفاده از آن می‌توانید به راحتی یک Producer را ایجاد کنید. در terminalی که قبلا برای مدیریت Topicها باز کرده‌اید با استفاده از دستور زیر، Producer را اجرا می‌کنیم:

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic userChat


توضیحات Optionها و آرگومانهایی که در دستور ایجاد Producer استفاده شدند به شرح زیر است:  

broker-list localhost:9092--:

نشان دهنده لیست Broker‌ها می‌باشد و در صورتیکه تعداد آنها بیش از یک باشد، می‌توان آنها را با "," از هم جدا کرد (پورت پیشفرض Kafka server برای دریافت دستورات، 9092 می‌باشد).

topic userChat--:

Topicی از Kafka server را مشخص می‌کند که این Producer می‌خواهد پیامهای خود را برایش ارسال کند.


پس از اجرای دستور فوق، با تایپ کردن و زدن کلید Enter، پیام مورد نظر به Broker موردنظر یعنی localhost:9092 ارسال و در تنها Partition تاپیک userChat ذخیره می‌شود.


اجرای یک Consumer و دریافت پیامهای موجود در Topic مورد نظر:

kafka-console-consumer.sh ابزاریست که خود Kafka در اختیار ما قرار داده است و با استفاده از آن می‌توانیم به راحتی یک Consumer برای  userChat ،Topic ایجاد کنیم.

در یک terminal جدید با استفاده از دستور زیر یک Consumer را ایجاد کنید که userChat  ،Topic را Subscribe می‌کند:

 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic userChat --from-beginning


توضیحات Optionها و آرگومانهایی که در دستور ایجاد یک Consumer استفاده شدند به شرح زیر است:  

bootstrap-server localhost:9092-- :

مشخص کننده Kafka serverی است که می‌خواهیم از طریق آن به تمامی اعضای Cluster دسترسی داشته باشیم. در صورتیکه بیش از یک سرور را بخواهیم وارد کنیم، باید آنها را با  "," از هم جدا کنیم.

topic userChat --:

مشخص کننده Topicی است که این Consumer میخواهد روی آن Subscribe کند.

from-beginning--:

 مشخص کننده این است که Consumer ایجاد شده می‌خواهد تمامی پیامهای موجود در userChat ، Topic را از اولین offset تا آخرین offset دریافت کند.

پس از اجرای کد فوق مشاهده می‌کنید که پیامهایی که قبلا Producer در تاپیک userChat ثبت کرده‌است، برای این Consumer ارسال می‌شوند.


از اینجا به بعد هر لحظه که در terminal ارسال کننده یا Producer پیامی تایپ کنید و کلید Enter را بزنید، بلافاصله پیام مورد نظر برای Consumer ارسال می‌شود و در terminal آن نمایش داده می‌شود. حتی می‌توانیم چندین Consumer را در terminal ‌های مختلفی اجرا کنیم. در این صورت با ارسال هر پیام از طرف Producer، تمامی Consumer‌ها آن را نمایش می‌دهند.


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

مطالب
معرفی JSON Web Token


دو روش کلی و پرکاربرد اعتبارسنجی سمت سرور، برای برنامه‌های سمت کاربر وب وجود دارند:
الف) Cookie-Based Authentication که پرکاربردترین روش بوده و در این حالت به ازای هر درخواست، یک کوکی جهت اعتبارسنجی کاربر به سمت سرور ارسال می‌شود (و برعکس).


ب) Token-Based Authentication که بر مبنای ارسال یک توکن امضاء شده به سرور، به ازای هر درخواست است.


مزیت‌های استفاده‌ی از روش مبتنی بر توکن چیست؟

 • Cross-domain / CORS: کوکی‌ها و CORS آنچنان با هم سازگاری ندارند؛ چون صدور یک کوکی وابسته‌است به دومین مرتبط به آن و استفاده‌ی از آن در سایر دومین‌ها عموما پذیرفته شده نیست. اما روش مبتنی بر توکن، وابستگی به دومین صدور آن‌را ندارد و اصالت آن بر اساس روش‌های رمزنگاری تصدیق می‌شود.
 • بدون حالت بودن و مقیاس پذیری سمت سرور: در حین کار با توکن‌ها، نیازی به ذخیره‌ی اطلاعات، داخل سشن سمت سرور نیست و توکن موجودیتی است خود شمول (self-contained). به این معنا که حاوی تمام اطلاعات مرتبط با کاربر بوده و محل ذخیره‌ی آن در local storage و یا کوکی سمت کاربر می‌باشد.
 • توزیع برنامه با CDN: حین استفاده از روش مبتنی بر توکن، امکان توزیع تمام فایل‌های برنامه (جاوا اسکریپت، تصاویر و غیره) توسط CDN وجود دارد و در این حالت کدهای سمت سرور، تنها یک API ساده خواهد بود.
 • عدم در هم تنیدگی کدهای سمت سرور و کلاینت: در حالت استفاده‌ی از توکن، این توکن می‌تواند از هرجایی و هر برنامه‌ای صادر شود و در این حالت نیازی نیست تا وابستگی ویژه‌ای بین کدهای سمت کلاینت و سرور وجود داشته باشد.
 • سازگاری بهتر با سیستم‌های موبایل: در حین توسعه‌ی برنامه‌های بومی پلتفرم‌های مختلف موبایل، کوکی‌ها روش مطلوبی جهت کار با APIهای سمت سرور نیستند. تطابق یافتن با روش‌های مبتنی بر توکن در این حالت ساده‌تر است.
 • CSRF: از آنجائیکه دیگر از کوکی استفاده نمی‌شود، نیازی به نگرانی در مورد حملات CSRF نیست. چون دیگر برای مثال امکان سوء استفاده‌ی از کوکی فعلی اعتبارسنجی شده، جهت صدور درخواست‌هایی با سطح دسترسی شخص لاگین شده وجود ندارد؛ چون این روش کوکی را به سمت سرور ارسال نمی‌کند.
 • کارآیی بهتر: حین استفاده‌ی از توکن‌ها، به علت ماهیت خود شمول آن‌ها، رفت و برگشت کمتری به بانک اطلاعاتی صورت گرفته و سرعت بالاتری را شاهد خواهیم بود.
 • امکان نوشتن آزمون‌های یکپارچگی ساده‌تر: در حالت استفاده‌ی از توکن‌ها، آزمودن یکپارچگی برنامه، نیازی به رد شدن از صفحه‌ی لاگین را ندارد و پیاده سازی این نوع آزمون‌ها ساده‌تر از قبل است.
 • استاندارد بودن: امروزه همینقدر که استاندارد JSON Web Token را پیاده سازی کرده باشید، امکان کار با انواع و اقسام پلتفرم‌ها و کتابخانه‌ها را خواهید یافت.


اما JWT یا JSON Web Token چیست؟

JSON Web Token یا JWT یک استاندارد وب است (RFC 7519) که روشی فشرده و خود شمول (self-contained) را جهت انتقال امن اطلاعات، بین مقاصد مختلف را توسط یک شیء JSON، تعریف می‌کند. این اطلاعات، قابل تصدیق و اطمینان هستند؛ از این‌رو که به صورت دیجیتال امضاء می‌شوند. JWTها توسط یک کلید مخفی (با استفاده از الگوریتم HMAC) و یا یک جفت کلید خصوصی و عمومی (توسط الگوریتم RSA) قابل امضاء شدن هستند.
در این تعریف، واژه‌هایی مانند «فشرده» و «خود شمول» بکار رفته‌اند:
 - «فشرده بودن»: اندازه‌ی شیء JSON یک توکن در این حالت کوچک بوده و به سادگی از طریق یک URL و یا پارامترهای POST و یا داخل یک HTTP Header قابل ارسال است و به دلیل کوچک بودن این اندازه، انتقال آن نیز سریع است.
 - «خود شمول»: بار مفید (payload) این توکن، شامل تمام اطلاعات مورد نیاز جهت اعتبارسنجی یک کاربر است؛ تا دیگر نیازی به کوئری گرفتن هر باره‌ی از بانک اطلاعاتی نباشد (در این روش مرسوم است که فقط یکبار از بانک اطلاعاتی کوئری گرفته شده و اطلاعات مرتبط با کاربر را امضای دیجیتال کرده و به سمت کاربر ارسال می‌کنند).


چه زمانی بهتر است از JWT استفاده کرد؟

اعتبارسنجی: اعتبارسنجی یک سناریوی متداول استفاده‌ی از JWT است. زمانیکه کاربر به سیستم لاگین کرد، هر درخواست بعدی او شامل JWT خواهد بود که سبب می‌شود کاربر بتواند امکان دسترسی به مسیرها، صفحات و منابع مختلف سیستم را بر اساس توکن دریافتی، پیدا کند. برای مثال روش‌های «Single Sign On» خود را با JWT انطباق داده‌اند؛ از این جهت که سربار کمی را داشته و همچنین به سادگی توسط دومین‌های مختلفی قابل استفاده هستند.
انتقال اطلاعات: توکن‌های با فرمت JWT، روش مناسبی جهت انتقال اطلاعات امن بین مقاصد مختلف هستند؛ زیرا قابل امضاء بوده و می‌توان اطمینان حاصل کرد که فرستنده دقیقا همانی است که ادعا می‌کند و محتوای ارسالی دست نخورده‌است.


ساختار یک JWT به چه صورتی است؟

JWTها دارای سه قسمت جدا شده‌ی با نقطه هستند؛ مانند xxxxx.yyyyy.zzzzz و شامل header، payload و signature می‌باشند.
الف) Header
Header عموما دارای دو قسمت است که نوع توکن و الگوریتم مورد استفاده‌ی توسط آن را مشخص می‌کند:
 {
   "alg": "HS256",
   "typ": "JWT"
}
نوع توکن در اینجا JWT است و الگوریتم‌های مورد استفاده، عموما  HMAC SHA256 و یا RSA هستند.

ب) payload
payload یا «بار مفید» توکن، شامل claims است. منظور از claims، اطلاعاتی است در مورد موجودیت مدنظر (عموما کاربر) و یک سری متادیتای اضافی. سه نوع claim وجود دارند:
Reserved claims: یک سری اطلاعات مفید و از پیش تعیین شده‌ی غیراجباری هستند؛ مانند:
iss یا صادر کنند (issuer)، exp یا تاریخ انقضاء، sub یا عنوان (subject) و aud یا مخاطب (audience)
 Public claims: می‌تواند شامل اطلاعاتی باشد که توسط IANA JSON Web Token Registry پیشتر ثبت شده‌است و فضاهای نام آن‌ها تداخلی نداشته باشند.
Private claims: ادعای سفارشی هستند که جهت انتقال داده‌ها بین مقاصد مختلف مورد استفاده قرار می‌گیرند.
یک نمونه‌ی payload را در اینجا ملاحظه می‌کنید:
 {
   "sub": "1234567890",
   "name": "John Doe",
   "admin": true
}
این اطلاعات (هم header و هم payload)، به صورت base64 انکد شده و به JWT اضافه می‌شوند.

ج) signature
یک نمونه فرمول محاسبه‌ی امضای دیجیتال پیام JWT به صورت ذیل است:
 HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
در اینجا بر اساس الگوریتم HMAC SHA256، هدر و بار مفید پیام به صورت base64 دریافت و به کمک یک کلید مخفی، محاسبه و به JWT اضافه می‌شود تا توسط آن بتوان اصالت پیام و فرستنده‌ی آن‌را تائید کرد. امضاء نیز در نهایت با فرمت base64 در اینجا انکد می‌شود:


یک نمونه مثال تولید این نوع توکن‌ها را در آدرس https://jwt.io می‌توانید بررسی کنید.
در این سایت اگر به قسمت دیباگر آن مراجعه کنید، برای نمونه قسمت payload آن قابل ویرایش است و تغییرات را بلافاصله در سمت چپ، به صورت انکد شده نمایش می‌دهد.


یک نکته‌ی مهم: توکن‌ها امضاء شده‌اند؛ نه رمزنگاری شده

همانطور که عنوان شد، توکن‌ها از سه قسمت هدر، بار مفید و امضاء تشکیل می‌شوند (header.payload.signature). اگر از الگوریتم HMACSHA256 و کلید مخفی shhhh برای امضای بار مفید ذیل استفاده کنیم:
 {
   "sub": "1234567890",
   "name": "Ado Kukic",
   "admin": true
}
یک چنین خروجی باید حاصل شود:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbyBLdWtpYyIsImFkbWluIjp0cnVlLCJpYXQiOjE0NjQyOTc4ODV9.Y47kJvnHzU9qeJIN48_bVna6O0EDFiMiQ9LpNVDFymM
در اینجا باید دقت داشت که هدر و بار مفید آن، صرفا با الگوریتم base64 انکد شده‌اند و این به معنای رمزنگاری نیست. به عبارتی می‌توان اطلاعات کامل هدر و بار مفید آن‌را به دست آورد. بنابراین هیچگاه اطلاعات حساسی را مانند کلمات عبور، در اینجا ذخیره نکنید.
البته امکان رمزنگاری توسط JSON Web Encryption نیز پیش بینی شده‌است (JWE).


از JWT در برنامه‌ها چگونه استفاده می‌شود؟

زمانیکه کاربر، لاگین موفقی را به سیستم انجام می‌دهد، یک توکن امن توسط سرور صادر شده و با فرمت JWT به سمت کلاینت ارسال می‌شود. این توکن باید به صورت محلی در سمت کاربر ذخیره شود. عموما از local storage برای ذخیره‌ی این توکن استفاده می‌شود؛ اما استفاده‌ی از کوکی‌ها نیز منعی ندارد. بنابراین دیگر در اینجا سشنی در سمت سرور به ازای هر کاربر ایجاد نمی‌شود و کوکی سمت سروری به سمت کلاینت ارسال نمی‌گردد.
سپس هر زمانیکه کاربری قصد داشت به یک صفحه یا محتوای محافظت شده دسترسی پیدا کند، باید توکن خود را به سمت سرور ارسال نماید. عموما اینکار توسط یک header سفارشی Authorization به همراه Bearer schema صورت می‌گیرد و یک چنین شکلی را دارد:
 Authorization: Bearer <token>
این روش اعتبارسنجی، بدون حالت (stateless) است؛ از این جهت که وضعیت کاربر، هیچگاه در سمت سرور ذخیره نمی‌گردد. API سمت سرور، ابتدا به دنبال هدر Authorization فوق، در درخواست دریافتی می‌گردد. اگر یافت شد و اصالت آن تائید شد، کاربر امکان دسترسی به منبع محافظت شده را پیدا می‌کند. نکته‌ی مهم اینجا است که چون این توکن‌ها «خود شمول» هستند و تمام اطلاعات لازم جهت اعطای دسترسی‌های کاربر به او، در آن وجود دارند، دیگر نیازی به رفت و برگشت به بانک اطلاعاتی، جهت تائید این اطلاعات تصدیق شده، نیست. به همین جهت کارآیی و سرعت بالاتری را نیز به همراه خواهند داشت.


نگاهی به محل ذخیره سازی JWT و نکات مرتبط با آن

محل متداول ذخیره‌ی JWT ها، در local storage مرورگرها است و در اغلب سناریوها نیز به خوبی کار می‌کند. فقط باید دقت داشت که local storage یک sandbox است و محدود به دومین جاری برنامه و از طریق برای مثال زیر دامنه‌های آن قابل دسترسی نیست. در این حالت می‌توان JWT را در کوکی‌های ایجاد شده‌ی در سمت کاربر نیز ذخیره کرد که چنین محدودیتی را ندارند. اما باید دقت داشت که حداکثر اندازه‌ی حجم کوکی‌ها 4 کیلوبایت است و با افزایش claims ذخیره شده‌ی در یک JWT و انکد شدن آن، این حجم ممکن است از 4 کیلوبایت بیشتر شود. بنابراین باید به این نکات دقت داشت.
امکان ذخیره سازی توکن‌ها در session storage مرورگرها نیز وجود دارد. session storage بسیار شبیه است به local storage اما به محض بسته شدن مرورگر، پاک می‌شود.
اگر از local storage استفاده می‌کنید، حملات Cross Site Request Forgery در اینجا دیگر مؤثر نخواهند بود. اما اگر به حالت استفاده‌ی از کوکی‌ها برای ذخیره‌ی توکن‌ها سوئیچ کنید، این مساله همانند قبل خواهد بود و مسیر است. در این حالت بهتر است طول عمر توکن‌ها را تاحد ممکن کوتاه تعریف کنید تا اگر اطلاعات آن‌ها فاش شد، به زودی بی‌مصرف شوند.


انقضاء و صدور مجدد توکن‌ها به چه صورتی است؟

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


حداکثر امنیت JWTها را چگونه می‌توان تامین کرد؟

- تمام توکن‌های خود را با یک کلید قوی، امضاء کنید و این کلید تنها باید بر روی سرور ذخیره شده باشد. هر زمانیکه سرور توکنی را از کاربر دریافت می‌کند، این سرور است که باید کار بررسی اعتبار امضای پیام رسیده را بر اساس کلید قوی خود انجام دهد.
- اگر اطلاعات حساسی را در توکن‌ها قرار می‌دهید، باید از JWE یا JSON Web Encryption استفاده کنید؛ زیرا JWTها صرفا دارای امضای دیجیتال هستند و نه اینکه رمزنگاری شده باشند.
- بهتر است توکن‌ها را از طریق ارتباطات غیر HTTPS، ارسال نکرد.
- اگر از کوکی‌ها برای ذخیره سازی آن‌ها استفاده می‌کنید، از HTTPS-only cookies استفاده کنید تا از Cross-Site Scripting XSS attacks در امان باشید.
- مدت اعتبار توکن‌های صادر شده را منطقی انتخاب کنید.
مطالب
پردازش‌های Async در Entity framework 6
اجرای Async اعمال نسبتا طولانی، در برنامه‌های مبتنی بر داده، عموما این مزایا را به همراه دارد:

الف) مقیاس پذیری سمت سرور

در اعمال سمت سرور متداول، تردهای متعددی جهت پردازش درخواست‌های کلاینت‌ها تدارک دیده می‌شوند. هر زمانیکه یکی از این تردها، یک عملیات blocking را انجام می‌دهد (مانند دسترسی به شبکه یا اعمال I/O)، ترد مرتبط با آن تا پایان کار این عملیات معطل خواهد شد. با بالا رفتن تعداد کاربران یک برنامه و در نتیجه بیشتر شدن تعداد درخواست‌هایی که سرور باید پردازش کند، تعداد تردهای معطل مانده نیز به همین ترتیب بیشتر خواهند شد. مشکل اصلی اینجا است که نمونه سازی تردها بسیار هزینه بر است (با اختصاص 1MB of virtual memory space) و منابع سرور محدود. با زیاد شدن تعداد تردهای معطل اعمال I/O یا شبکه، سرور مجبور خواهد شد بجای استفاده مجدد از تردهای موجود، تردهای جدیدی را ایجاد کند. همین مساله سبب بالا رفتن بیش از حد مصرف منابع و حافظه برنامه می‌گردد. یکی از روش‌های رفع این مشکل بدون نیاز به بهبودهای سخت افزاری، تبدیل اعمال blocking نامبرده شده به نمونه‌های non-blocking است. به این ترتیب ترد پردازش کننده‌ی این اعمال Async بلافاصله آزاد شده و سرور می‌تواند از آن جهت پردازش درخواست دیگری استفاده کند؛ بجای اینکه ترد جدیدی را وهله سازی نماید.

ب) بالا بردن پاسخ دهی کلاینت‌ها

کلاینت‌ها نیز اگر مدام درخواست‌های blocking را به سرور جهت دریافت پاسخی ارسال کنند، به زودی به یک رابط کاربری غیرپاسخگو خواهند رسید. برای رفع این مشکل نیز می‌توان از توانمندی‌های Async دات نت 4.5 جهت آزاد سازی ترد اصلی برنامه یا همان ترد UI استفاده کرد.

و ... تمام این‌ها یک شرط را دارند. نیاز است یک چنین API خاصی که اعمال Async واقعی را پشتیبانی می‌کنند، فراهم شده باشد. بنابراین صرفا وجود متد Task.Run، به معنای اجرای واقعی Async یک متد خاص نیست. برای این منظور ADO.NET 4.5 به همراه متدهای Async ویژه کار با بانک‌های اطلاعاتی است و پس از آن Entity framework 6 از این زیر ساخت استفاده کرده‌است که در ادامه جزئیات آن‌را بررسی خواهیم کرد.


پیشنیازها

برای کار با امکانات جدید Async موجود در EF 6 نیاز است از VS 2012 به بعد که به همراه کامپایلری است که واژه‌های کلیدی async و await را پشتیبانی می‌کند و همچنین دات نت 4.5 استفاده کرد. چون ADO.NET 4.5 اعمال async واقعی را پشتیبانی می‌کند، دات نت 4 در اینجا قابل استفاده نخواهد بود.


متدهای الحاقی جدید Async در EF 6.x

جهت متدهای الحاقی متداول EF مانند ToList، Max، Min و غیره، نمونه‌های Async آن‌ها نیز اضافه شده‌اند:
 QueryableExtensions:
AllAsync
AnyAsync
AverageAsync
ContainsAsync
CountAsync
FirstAsync
FirstOrDefaultAsync
ForEachAsync
LoadAsync
LongCountAsync
MaxAsync
MinAsync
SingleAsync
SingleOrDefaultAsync
SumAsync
ToArrayAsync
ToDictionaryAsync
ToListAsync

DbSet:
FindAsync

DbContext:
SaveChangesAsync

Database:
ExecuteSqlCommandAsync
بنابراین اولین قدم تبدیل کدهای قدیمی به Async، استفاده از متدهای الحاقی فوق است.


چند مثال


فرض کنید، مدل‌های برنامه، رابطه‌ی one-to-many ذیل را بین یک کاربر و مقالات او دارند:
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; }
    }

    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public int UserId { get; set; }
    }
همچنین Context برنامه نیز جهت در معرض دید قرار دادن این کلاس‌ها، به نحو ذیل تشکیل شده‌است:
    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<BlogPost> BlogPosts { get; set; }

        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }
    }
بر این اساس مثالی که دو رکورد را در جداول کاربران و مقالات به صورت async ثبت می‌کند، به نحو ذیل خواهد بود:
        private async Task<User> addUserAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var user = context.Users.Add(new User
                {
                    Name = "Vahid"
                });
                context.BlogPosts.Add(new BlogPost
                {
                    Content = "Test",
                    Title = "Test",
                    User = user
                });
                await context.SaveChangesAsync(cancellationToken);
                return user;
            }
        }

چند نکته جهت یادآوری مباحث Async

- به امضای متد واژه‌ی کلیدی async اضافه شده‌است، زیرا در بدنه‌ی آن از کلمه‌ی کلیدی await استفاده کرده‌ایم (لازم و ملزوم هستند).
- به انتهای نام متد، کلمه‌ی Async اضافه شده‌است. این مورد ضروری نیست؛ اما به یک استاندارد و قرارداد تبدیل شده‌است.
- مدل Async دات نت 4.5 مبتنی بر Taskها است. به همین جهت اینبار خروجی‌های توابع نیاز است از نوع Task باشند و آرگومان جنریک آن‌ها، بیانگر نوع مقداری که باز می‌گردانند.
- تمام متدهای الحاقی جدیدی که نامبرده شدند، دارای پارامتر اختیاری لغو عملیات نیز هستند. این مورد را با مقدار دهی cancellationToken در کدهای فوق ملاحظه می‌کنید.
نمونه‌ای از نحوه‌ی مقدار دهی این پارامتر در ASP.NET MVC به صورت زیر می‌تواند باشد:
 [AsyncTimeout(8000)]
public async Task<ActionResult> Index(CancellationToken cancellationToken)
در اینجا به امضای اکشن متد جاری، async اضافه شده‌است و خروجی آن نیز به نوع Task تغییر یافته است. همچنین یک پارامتر cancellationToken نیز تعریف شده‌است. این پارامتر به صورت خودکار توسط ASP.NET MVC پس از زمانیکه توسط ویژگی AsyncTimeout تعیین شده‌است، تنظیم خواهد شد. به این ترتیب، اعمال async در حال اجرا به صورت خودکار لغو می‌شوند.
- برای اجرا و دریافت نتیجه‌ی متدهای Async دار EF، نیاز است از واژه‌ی کلیدی await استفاده گردد.

استفاده کننده نیز می‌تواند متد addUserAsync را به صورت زیر فراخوانی کند:
 var user = await addUserAsync();
Console.WriteLine("user id: {0}", user.Id);

شبیه به همین اعمال را نیز جهت به روز رسانی و یا حذف اطلاعات خواهیم داشت:
        private async Task<User> updateAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var user1 = await context.Users.FindAsync(cancellationToken, 1);
                if (user1 != null)
                    user1.Name = "Vahid N.";

                await context.SaveChangesAsync(cancellationToken);
                return user1;
            }
        }

        private async Task<int> deleteAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var user1 = await context.Users.FindAsync(cancellationToken, 1);
                if (user1 != null)
                    context.Users.Remove(user1);

                return await context.SaveChangesAsync(cancellationToken);
            }
        }

کدهای Async تقلبی!

به قطعه کد ذیل دقت کنید:
         public async Task<List<TEntity>> GetAllAsync()
   {
    return await Task.Run(() => _tEntities.ToList());
   }
این متد از یکی از Generic repositoryهای فله‌ای رها شده در اینترنت انتخاب شده‌است.
به این نوع متدها که از Task.Run برای فراخوانی متدهای همزمان قدیمی مانند ToList جهت Async جلوه دادن آن‌ها استفاده می‌شود، کدهای Async تقلبی می‌گویند! این عملیات هر چند در یک ترد دیگر انجام می‌شود اما هم سربار ایجاد یک ترد جدید را به همراه دارد و هم عملیات ToList آن کاملا blocking است.
معادل صحیح Async واقعی این عملیات را در ذیل مشاهده می‌کنید:
        private async Task<List<User>> getUsersAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                return await context.Users.ToListAsync(cancellationToken);
            }
        }
متد ToListAsync یک متد Async واقعی است و نه شبیه سازی شده توسط Task.Run. متدهای Async واقعی کار با شبکه و اعمال I/O، از ترد استفاده نمی‌کنند و توسط سیستم عامل به نحو بسیار بهینه‌ای اجرا می‌گردند.
برای مثال پشت صحنه‌ی متد الحاقی SaveChangesAsync به یک چنین متدی ختم می‌شود:
 internal override async Task<long> ExecuteAsync(
//...
rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
//...
متد ExecuteNonQueryAsync جزو متدهای ADO.NET 4.5 است و برای اجرا نیاز به هیچ ترد جدیدی ندارد.
و یا برای شبیه سازی ToListAsync با ADO.NET 4.5 و استفاده از متدهای Async واقعی آن، به یک چنین کدهایی نیاز است: 
    var connectionString = "........";
    var sql = @"......"";
    var users = new List<User>();
 
    using (var cnx = new SqlConnection(connectionString))
    {
      using (var cmd = new SqlCommand(sql, cnx))
      {
       await cnx.OpenAsync(); 
       using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
       {
        while (await reader.ReadAsync())
        {
          var user = new User
          {
           Id = reader.GetInt32(0), 
           Name = reader.GetString(1), 
          };
         users.Add(user);
        }
       }
      }
    }


محدودیت پردازش موازی اعمال در EF

در متد ذیل، دو Task غیرهمزمان تعریف شده‌اند و سپس با await Task.WhenAll درخواست اجرای همزمان و موازی آن‌ها را کرده‌ایم:
        // multiple operations
        private static async Task loadAllAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var context = new MyContext())
            {
                var task1 = context.Users.ToListAsync(cancellationToken);
                var task2 = context.BlogPosts.ToListAsync(cancellationToken);

                await Task.WhenAll(task1, task2);
                // use task1.Result
            }
        }
این متد ممکن است اجرا شود؛ یا در بعضی از مواقع با استثنای ذیل خاتمه یابد:
  An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll
 Additional information: A second operation started on this context before a previous asynchronous operation completed.
Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context.
Any instance members are not guaranteed to be thread safe.
متن استثنای ارائه شده بسیار مفید است و توضیحات کامل را به همراه دارد. در EF در طی یک Context اگر عملیات Async شروع شده‌ای خاتمه نیافته باشد، مجاز به شروع یک عملیات Async دیگر، به موازت آن نخواهیم بود. برای رفع این مشکل یا باید از چندین Context استفاده کرد و یا await Task.WhenAll را حذف کرده و بجای آن واژه‌ی کلیدی await را همانند معمول، جهت صبر کردن برای دریافت نتیجه‌ی یک عملیات غیرهمزمان استفاده کنیم.