<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های وحید چشمی</title>
        <link>https://virgool.io/feed/@v.cheshmy</link>
        <description>ســلام، من وحید هستم، چند سالی هست که دستم رو کیبورده و کد میزنم. دوست دارم چیزی که تجربه میکنم رو با شما به اشتراک بزارم.https://youtube.com/@devlife013</description>
        <language>fa</language>
        <pubDate>2026-06-16 11:36:35</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/1259876/avatar/SYqlVw.png?height=120&amp;width=120</url>
            <title>وحید چشمی</title>
            <link>https://virgool.io/@v.cheshmy</link>
        </image>

                    <item>
                <title>معماری vertical slice در NET8.</title>
                <link>https://virgool.io/@v.cheshmy/%D9%85%D8%B9%D9%85%D8%A7%D8%B1%DB%8C-vertical-slice-%D8%AF%D8%B1-net8-bxciorlaogzd</link>
                <description>معرفیبسیاری از ما با معماری تمیز آشنا هستیم، امروزه به این رویکرد معماری لایه ای سنتی گفته می شود.یک معماری سنتی لایه‌ای یا پیازی کد را بر اساس technical concern در لایه‌های مختلف سازمان‌دهی می‌کند. در این رویکرد، هر لایه مسئولیت فردی در سیستم دارد.سپس لایه ها به یکدیگر وابسته هستند و می توانند با یکدیگر همکاری کنند.در برنامه های وب، لایه های زیر ممکن است اعمال شوند:ارائه(Presentation)کاربرد(Application)دامنه(Domain)زیر ساخت(Infrastructure)رویکرد معماری پیاز  مشکلات معماری لایه ای سنتیبه شدت به لایه‌ها متصل است، به آن بستگی دارد و قابلیت نگهداری مشکل خواهد بودتغییر موازی بین توسعه دهندگان ممکن است دشوار باشد، به این معنی که با تغییر بخش های مختلف، برنامه ممکن است دچار اختلال شود.معماری برش عمودیمعماری برش عمودی یک الگوی معماری است که کدها را به جای الگوهای فنی بر اساس ویژگی ها سازماندهی می کند.معماری برش عمودیهمانطور که در تصویر می بینید، ایده معماری برش عمودی در مورد گروه بندی کدها بر اساس عملکردهای تجاری است و همه کدهای مربوطه را در کنار هم قرار می دهد.در معماری لایه ای هدف این است که به لایه های افقی فکر کنیم، اما در معماری عمودی باید به لایه های عمودی فکر کنیم، در این مورد باید همه چیز را به عنوان یک ویژگی قرار دهیم، یعنی نیازی به لایه های مشترک مانند repositories, services, infrastructure, و حتی controllers نداریم و فقط باید روی ویژگی ها تمرکز کنیم.توسعه:حالا بیایید نگاهی بیندازیم که چگونه می‌توانیم از این رویکرد پیروی کنیم، من یک minimal API ساده در NET 8 ایجاد خواهم کرد.در اینجا راه حل ما شبیه خواهد بود:راه حل معماری برش عمودیمرحله 1: بسته های لازمما باید بسته های زیر را نصب کنیمdotnet add package MediatR 
dotnet add Microsoft.EntityFrameworkCore
dotnet add Microsoft.EntityFrameworkCore.Design
dotnet add Microsoft.EntityFrameworkCore.SqlServerمرحله 2:Entity را اضافه کنیدبیایید با ایجاد یک موجودیت شروع کنیم.public class Book
{
 public long  Id { get; set; }
 public string Name { get; set; } = string.Empty;
 public string Description { get; set; } = string.Empty;
}مرحله 3: پیکربندی DbContextباید dbContext را در پوشه پایگاه داده اضافه کنیمpublic class ApplicationDbContext:DbContext
{
 public ApplicationDbContext(DbContextOptions&lt;ApplicationDbContext&gt; options):base(options)
    {
 
    }
 public DbSet&lt;Book&gt; Books { get; set; }
}مرحله 4: سرویس ها و میان افزارها را پیکربندی کنیدزمان آن است که سرویس ها را پیکربندی کنیم و همچنین middleware را در API خود اضافه کنیمappsettings.Development.json{ 
 &amp;quotLogging&amp;quot :  { 
 &amp;quotLogLevel&amp;quot :  { 
 &amp;quotDefault&amp;quot :  &amp;quotInformation&amp;quot , 
 &amp;quotMicrosoft.AspNetCore&amp;quot :  &amp;quotWarning&amp;quot 
 } 
 } , 
 &amp;quotConnectionStrings&amp;quot :  { 
 &amp;quotDatabase&amp;quot :  &amp;quotServer=localhost;Database=VSA;Integrated&amp;quot Security=true;MultipleActiveResultSets=true;TrustServerCertificate=Yes;&amp;quot 
 } 
}Program.csvar builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext&lt;ApplicationDbContext&gt;(opt =&gt;
{        opt.UseSqlServer(builder.Configuration.GetConnectionString(&amp;quotDatabase&amp;quot));
});
builder.Services.AddMediatR(cfg =&gt; cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
var app = builder.Build();
app.Run();بنابراین، ما فقط dbContext را برای استفاده از سرور SQL و همچنین سرویس MediatR را ثبت کردیم.مرحله 5: ایجاد ویژگی کتابمرحله بعدی این است که ویژگی را در معماری خود اعمال کنیم، در این مورد باید هر ویژگی را از هم جدا کنیم، در این مثال یک ویژگی کتاب ایجاد می کنم که همه کتاب ها را ایجاد کرده و دریافت می کند.بیایید با ایجاد ویژگی کتاب شروع کنیم، زیرا من از الگوی MediatR استفاده خواهم کرد، الگوی CQRS را تطبیق خواهم داد که پرس و جو و دستورات را از هم جدا می کند.public static class CreateBook
{
 public sealed class CreateBookCommand:IRequest&lt;long&gt;
    {
 public string Name { get; set; } = string.Empty;
 public string Description { get; set; } = string.Empty;
    }
 internal sealed class Handler : IRequestHandler&lt;CreateBookCommand, long&gt;
    {
 private readonly ApplicationDbContext _dbContext;
 public Handler(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }
 public async Task&lt;long&gt; Handle(CreateBookCommand request, CancellationToken cancellationToken)
        {
 var book = new Book
            {
                Name = request.Name,
                Description = request.Description
            };
 await _dbContext.AddAsync(book, cancellationToken);
 await _dbContext.SaveChangesAsync(cancellationToken);
 return book.Id;
        }
    }
 public static void AddEndpoint(this IEndpointRouteBuilder app)
    {
            app.MapPost(&amp;quotapi/books&amp;quot, async (CreateBookRequest request, ISender sender) =&gt;
            {
 var bookId = await sender.Send(request);
 return Results.Ok(bookId);
            });
    }
}قراردادهاpublic class CreateBookRequest
{
 public string Name { get; set; } = string.Empty;
 public string Description { get; set; } = string.Empty;
}همانطور که می بینید، همه ویژگی را در کلاس static اضافه کردیم، یک ورودی ایجاد می کند که از IRequest برای ایجاد یک دستور استفاده می کند، سپس IRequest را به کنترل کننده آن اضافه می کند و کتاب را ایجاد می کند و در نهایت نقطه پایانی را برای فراخوانی توسط مشتری.برای اعمال نقطه پایانی که به این میان افزار در خط لوله نیاز داریم، به سادگی می توانیم آن را در کلاس program.cs اضافه کنیم.var app = builder.Build(); 
CreateBook.AddEndpoint(app); 
...مرحله 6: قابلیت دریافت همه کتاب هابیایید یک پرس و جو ایجاد کنیم تا همه کتاب ها را انتخاب کرده و به مشتری برگردانیم.public static class GetAllBooks
{
 public sealed class Query : IRequest&lt;List&lt;Book&gt;&gt;
    {
    }
 internal sealed class QueryHandler : IRequestHandler&lt;Query, List&lt;Book&gt;&gt;
    {
 private readonly ApplicationDbContext _dbContext;
 public QueryHandler(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }
 public async Task&lt;List&lt;Book&gt;&gt; Handle(Query request, CancellationToken cancellationToken)
            =&gt; await _dbContext.Books.ToListAsync(cancellationToken: cancellationToken);
    }
 public static void AddEndpoint(this IEndpointRouteBuilder app)
    {
        app.MapGet(&amp;quotapi/books&amp;quot, async (ISender sender) =&gt;
        {
 var books=await sender.Send(new GetAllBooks.Query());
 return Results.Ok(books);
        });
    }
}همچنین، باید Endpoint را در Pipeline اضافه کنیم:var app = builder.Build();
CreateBook.AddEndpoint(app);
GetAllBooks.AddEndpoint(app);
...مسئله:همانطور که می بینید، برای هر ویژگی که نیاز داریم میان افزار endpoint را اضافه کنیم، در اینجا بسته دیگری وجود دارد که می توانیم نصب کرده و از آن برای خلاص شدن از اضافه کردن میان افزار جدید استفاده کنیم.dotnet add package Carterبرای استفاده از کارتر، باید ویژگی ها را اصلاح کنیمCreateBook.cspublic static class CreateBook
{
 public sealed class CreateBookCommand:IRequest&lt;long&gt;
    {
 public string Name { get; set; } = string.Empty;
 public string Description { get; set; } = string.Empty;
    }
 internal sealed class Handler : IRequestHandler&lt;CreateBookCommand, long&gt;
    {
 private readonly ApplicationDbContext _dbContext;
 public Handler(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }
 public async Task&lt;long&gt; Handle(CreateBookCommand request, CancellationToken cancellationToken)
        {
 var book = new Book
            {
                Name = request.Name,
                Description = request.Description
            };
 await _dbContext.AddAsync(book, cancellationToken);
 await _dbContext.SaveChangesAsync(cancellationToken);
 return book.Id;
        }
    }
}
public class CreateBookEndpoint : ICarterModule
{
 public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapPost(&amp;quotapi/books&amp;quot, async (CreateBookRequest request, ISender sender) =&gt;
        {
 var bookId = await sender.Send(request);
 return Results.Ok(bookId);
        });
    }
}GetAllBooks.cs
public static class GetAllBooks
{
 public sealed class Query : IRequest&lt;List&lt;Book&gt;&gt;
    {
    }
 internal sealed class QueryHandler : IRequestHandler&lt;Query, List&lt;Book&gt;&gt;
    {
 private readonly ApplicationDbContext _dbContext;
 public QueryHandler(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }
 public async Task&lt;List&lt;Book&gt;&gt; Handle(Query request, CancellationToken cancellationToken)
            =&gt; await _dbContext.Books.ToListAsync(cancellationToken: cancellationToken);
    }
}
public class GetAllBooksEndpoint : ICarterModule
{
 public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapGet(&amp;quotapi/books&amp;quot, async (ISender sender) =&gt;
        {
 var books=await sender.Send(new GetAllBooks.Query());
 return Results.Ok(books);
        });
    }
}و آخرین مرحله ثبت Carter در Middleware استvar app = builder.Build(); 
app.MapCarter();ما فقط باید میان افزار MapCarter را در خط لوله اضافه کنیم و تمام است، نیازی به اضافه کردن ویژگی جدید در خط لوله نیست.نظر شما در مورد این ویژگی جدید چیست؟ آیا آن را به عنوان یک افزودنی ارزشمند برای پروژه های خود می بینید یا قصد دارید به معماری سنتی لایه ای/پیازی پایبند باشید؟ از کد نویسی لذت ببرید!!!</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Fri, 15 Sep 2023 18:20:12 +0330</pubDate>
            </item>
                    <item>
                <title>پردازش درخواست در ASP.NET Core</title>
                <link>https://virgool.io/@v.cheshmy/%D9%BE%D8%B1%D8%AF%D8%A7%D8%B2%D8%B4-%D8%AF%D8%B1%D8%AE%D9%88%D8%A7%D8%B3%D8%AA-%D8%AF%D8%B1-aspnet-core-zoofvvfey9ug</link>
                <description>در این مقاله توضیحاتی را در خصوص نحوه پردازش یک درخواست در  ASP.NET Core ارائه خواهم داد.چرخه پردازش درخواستزمانی که یک درخواست توسط کاربر ایجاد میشود، ابتدا توسط یک وب سرور دریافت و به وب سرور داخلی NET Core یعنی Kestrel ارسال می شود، بعد از دریافت درخواست (http context) توسط Kestrel آن را در مسیر اجرا قرار میدهد، این فرایند پردازش را pipeline میگویند، pipeline دارای یک سری قطعات اجرایی هستند که به‌صورت زنجیره ای به یکدیگر متصل شده اند، این قطعات را middleware میگویند. این قطعات دارای یک ورودی و یک خروجی هستند، بعد از دریافت Http context هر middleware آن را پردازش می کند و به Middleware بعدی ارسال میکند.در نهایت یک قطعه نهایی به نام endpoint middleware وجود دارد که خروجی قطعه قبلی را دریافت و پردازش میکند، پس از پردازش نتیجه را به middleware قبلی ارسال  میکند تا به قطعه اول برسد، سپس قطعه اول درخواست پردازش شده را به kestrel و به وب سرور و در نهایت به کاربر درخواست کننده ارسال میکند.تعریف http contextهمانطور که در شکل بالا ملاحظه میکنید، زمانی که کاربر یک درخواست را ارسال میکند، در پشت زمینه یک نمونه از http context ایجاد می شود. http context شامل مقادیر متفاوتی از اطلاعات شامل  http request و https response ،اطلاعات کاربر، session، و اطلاعات ارتباطی نیز می باشد. برای بررسیاطلاعات http requestهر درخواست http شامل سه قسمت می باشد، request line، request header و body. البته اطلاعات body می تواند خالی باشد.اطلاعات request lineمتدی را مشخص می کند که به سرور اطلاع رسانی میکند که با درخواست چه کاری باید انجام دهد.شامل اطلاعاتی در خصوص URL است که برای پیدا کردن سرور مورد استفاده قرار می گیرد.همچنین شامل اطلاعاتی در خصوص پروتکل HTTP می باشد، به هنوان مثال HTTP/1.0 یا HTTP/1.1اطلاعات request headerشامل صفر یا چند header می باشد، از header ها برای ارسال اطلاعات بیشتر در مورد درخواست استفاده می شود. به عنوان مثال، نوع content-type که کاربر در خواست میدهد چه چیزی باید باشد.اطلاعات request bodyهمانطور که اشاره کردم، این اطلاعات می توانند اختیاری باشند که به سرور ارسال می شوند. دلیل اختیاری بودن آن این است که فرض کنید شما یک سری اطلاعات را باید در پایگاه داده ثبت کنید، بدین منظور از طریق body آن اطلاعات را ارسال میکنید، اما در فرایند دیگر شما تنها نیازی دارید تا یک سری اطلاعات را واکشی و به کاربر نمایش دهید، بدین منظور نیازی به ارسال اطلاعات در body نمی باشد.اطلاعات http responseشامل اطلاعاتی است که پس از پردازش توسط سروربه کلاینت بازگردانده می شود، که شامل status line، response header و response body می باشد.اطلاعات Status lineاین اطلاعات شامل نسخه HTTP ، وضعیت کد، و status text می باشد.اطلاعات response headerشامل یک یا چند response header است که برای ارسال اطلاعات اضافه از سمت client به server استفاده می شود.اطاعات response bodyشامل اطلاعاتی است که توسط کلاینت درخواست شده است، به عنوان مثال دریافت کاربر با کد ملی، دریافت اطلاعات یک کاربر با ایمیل و... اگر درخواست موفقیت آمیز نباشد، سپس response body شامل دلایل خطاهای به‌وجود آمده خواهد بود.فرایند اجرای یک برنامه در ASP.NET Coreاگر یک پروژه ASP.NET Core ایجاد کنیم، در فایل program.cs کدهای زیر را خواهیم دید:var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();
app.MapGet(&amp;quot/&amp;quot, () =&gt; &amp;quotHello World!&amp;quot);

app.Run();میتوان گفت فرایند اجرای برنامه به دو قسمت سرویس ها و pipeline تقسیم بندی می شوند، اولین فرایند، ثبت سرویس ها و وابستگی ها است که توسط webapplication builder ایجاد میشود  بعد از اینکه آن را build میکنیم، یک application در اختیار داریم. که اجرای برنامه را در چرخه pipeline قرار خواهد داد.تعریف Kestrelیک وب سرور داخلی cross-platform در ASP.NET Core میباشد که در زمان توسعه NET Core. ایجاد می شود.تعریف سرویسهر کلاس یک کار را انجام میدهد، مثلا سرویس repositoy که کار انجام تراکنش در DB را بر عهده دارد، یا کلاس Loggerطول عمر اشیا را بهasp.net core میسپاریم که به آن سرویس میگوییم.var builder = WebApplication.CreateBuilder(args);قطعه کد بالا نشان می دهد که در زمان اجرای برنامه ابتدا یک builder توسط CreateBuilder ایجاد میشود، همانطور که در بالا اشاره کردم، زمانی که یک پروژه ASP.NET Core ایجاد می کنیم، وب سرویس kestrel به صورت پیشفرض نیز داخل آن اجرا خواهد شد ،بدین صورت که بعد از اجرای دستور CreateBuilder ، این متد به صورت داخلی از UseKestrel استفاده می کند.برای اینکه یک سرویس را در برنامه ثبت کنیم باید از builder.Services استفاده کنیم، به عنوان مثال برای فعال کردن احراز هویت باید از دستور زیر استفاده کنیم.builder.Services.AddAuthentication()تعریف pipelineبعد از اینکه تمامی سرویس های خود را ثبت کردیم نوبت به buildکردن آن و قرار دادن آنها در چرخه pipeline میباشد.var app = builder.Build();پایپلاین با چند middleware ساخته می شود، در اصل pipeline یک مسیر است، اما middleware بخش اجرایی از برنامه است. برای پیاده‌سازیmiddleware از app.use استفاده میکنیم، که دارای دو پارامتر ورودی است، اولین ورودی آن http context است و ورودی دوم آن middleware بعدی است که قرار است اجرا شود. app.Use(async (httpContext,next)=&gt;
{
       //middlware logics
});همانطور که قبلا توضیح دادم، middleware ها به‌صورت زنجیره ای به هم متصل هستند، برای اینکه مشخص کنیم کدام قطعه قرار است در pipeline اجرا شود از next استفاده میکنیم.استفاده از UseMiddlewareاگر به کد بالا بازگردیم، درمیابیم که برای ایجاد کردن middleware در program.cs نیاز است تا شاید صدها خط کد داخل آن بنویسیم، اما روش دیگری نیز وجود دارد که میتوان منطق middleware را به آن محول کرد و کدهای خود را در آن قسمت توسعه داد، بدین منظور میتوان از UseMiddleware استفاده کرد.برای ایجاد یک middleware ابتدا نیاز داریم یک کلاس به‌صورت زیر ایجاد کنیم، این کلاس باید چندین ویژگی داشته باشد.باید دارای یک constructor باشد و یک delegate باید به آن ارسال کرد.ایجاد یک متد با نام Invoke که http context را در پارامتر ورودی خود داشته باشد.public class ClassMiddleware
{
          private readonly RequestDelegate _next;
          public ClassMiddleware (RequestDelegate  next)=&gt;_next=next;
          public async Task Invoke(HttpContext context)
          {
              // logic code omited for brevity
              await _next.Invoke(context);
           }
}و برای استفاده از این middleware باید در program.cs خود از دستور UseMiddleware به‌صورت زیر استفاده کنیم.app.UseMiddleware&lt;ClassMiddleware&gt;()ارائه خدمات و اجرا تمامی دستوراتی که در بالا نوشتیم از ثبت سرویس ها، ثبت وابستگی ها  تا ایجاد middlewareها و قرار دادن آنها در مسیر pipeline تنها ثبت و ایجاد آنها بود، اما برای اجرای آنها باید از ()app.Run استفاده کنیم.زمانی که app.run را اجرا میکنیم، تعدای hosted service اجرا میشوند، که به صورت long running task است.این task ها زمانی که باز می شوند، به چرخه ی اجرای خود ادامه میدهند، اما بر خلاف آن پروژه های Console application زمانی که به خط انتهایی می رسند چرخه عمر آنها نیز پایان میپذیرد.میتوان گفت یک پروژه ASP.NET Core یک console applicaion بی انتها میباشد.خلاصهدر این مقاله آموختیم که فرایند درخواست به چه صورتی پردازش میشوند، با مفاهیم http context، http، سرویس ها، middleware و pipeline آشنا شدیم.دریافتیم که kestrel چیست و به چه منظوری مورد استفاده قرار میگیرد.لطفا برای مشاهده آموزش های ویدیویی به این لینک مراجعه کنید.ممنونم بابت لایک، کامنت و دنبال کردن من، باعث میشه با قدرت بیشتری ادامه بدم :)از کدنویسی لذت ببرید!!!</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Sun, 06 Aug 2023 15:29:21 +0330</pubDate>
            </item>
                    <item>
                <title>نسخه بندی API در ASP.NET Core</title>
                <link>https://virgool.io/@v.cheshmy/api-versioning-yisxoyzhu9ua</link>
                <description>در این مقاله در خصوص پیاده‌سازی API Versioning در ASP.NET Core خواهیم آموخت.چرا نسخه بندیاولین سؤال اینجا مطرح می شود که چرا ما باید API های خود را نسخه بندی کنیم؟ اگر قرار به توسعه ویژگی جدید باشد معمولا API کنونی خود را بروز رسانی میکنیم، اما این تغییرات گاهی میتواند مشکلات زیادی را همراه داشته باشند.نسخه بندی چیستنسخه بندی API  استراتژی است برای نگه داشتن وضعیت کنونی API شما  به همراه ارائه ویژگی های جدید تر.چرا نسخه بندی مورد نیاز استیکی از ویژگی های مهم API Versioning قابلیت بازگشت پذیری (backward compatibility) است، بدین معنا که شما با کمترین هزینه این امکان را فراهم میکنید تا در نزدیک ترین زمان ممکن به نسخه قبل API خود بازگردید.یکی دیگر از مزایا استفاده از این ویژگی این است که شما به کاربران برنامه خود این اجازه را میدهید تا با مرور زمان به نسخه جدید مهاجرت کنند.توسعهدر این مقاله قصد دارم 4 راه متفاوت پیاده‌سازی API Versioning را شرح دهم، و شما بنا به برنامه خود میتوانید از آن استفاده کنید.قدم 1 ایجاد یک پروژهبرای API Versioning نیاز به یک ASP.NET Core web API داریم،قدم 2 نصب کتابخانه مورد نیاز باید کتابخانه مربوط به آن را نصب کنیم.Microsoft.AspNetCore.Mvc.Versioning.ApiExplorerقدم 3 پیکربندی سرویس Api versioning  باید تنظیمات مربوط به  سرویس Api versioning را در program.cs ثبت کنیم.builder.Services.AddApiVersioning(o =&gt;
{
    o.AssumeDefaultVersionWhenUnspecified = true;
    o.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0);
    o.ReportApiVersions = true;
    o.ApiVersionReader = ApiVersionReader.Combine(
        new QueryStringApiVersionReader(&amp;quotapi-version&amp;quot),
        new HeaderApiVersionReader(&amp;quotX-Version&amp;quot),
        new MediaTypeApiVersionReader(&amp;quotver&amp;quot)
    );
});همانطور که بالا اشاره کردم راههای متفاوتی برای استفاده از نسخه بندی API شما وجود دارد.با استفاده از پراپرتی های AssumeDefaultVersionWhenUnspecified و DefaultApiVersionاگر کلاینت، نسخه مورد نظر را در درخواست خود ارسال نکرد، به صورت پیشفرض نسخه آن را 1 در نظر میگیریم.با پراپرتیReportApiVersionsاین اجازه را میدهیم تا از نسخه های مختلف مورد پشتیبانیAPI استفاده کنیم، که این ویژگی امکان های api-supported-versions و api-deprecated-versions را فراهم میکند.در نهایت، به این دلیل که قرار است برنامه ما نسخه های مختلف را پشتیبانی کند، با استفاده از ApiVersionReader این امکان را فراهم میکنیم تا با راههای مختلف امکان ارسال درخواست خود را داشته باشیم.قدم 4 پیاده‌سازی کنترلرروش 1: استفاده از Query String برای دسترسی به Endpointاولین روشی که میتوانیم استفاده کنیم استفاده از Query String است، برای این منظور کنترلر زیر را پیاده‌سازی میکنیم:[ApiController]
[Route(&amp;quot[controller]&amp;quot)]
[ApiController]
[Route(&amp;quot[controller]&amp;quot)]
[ApiVersion(&amp;quot1.0&amp;quot)]
public class EmployeeController:ControllerBase
{
    private readonly IMediator _mediator;

    public EmployeeController(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    [HttpGet]
    [Route(&amp;quotGetEmployeeById&amp;quot)]
   
    public async Task&lt;IActionResult&gt; GetEmployeeById( long id)
    {
        var employee =await _mediator.Send(new GetEmployeeByIdQuery(id));
        return Ok(employee);
    }
    
    [HttpGet]
    [Route(&amp;quotGetEmployeeByEmail&amp;quot)]
    [ApiVersion(&amp;quot2.0&amp;quot)]
    public async Task&lt;IActionResult&gt; GetEmployeeByEmail( string email)
    {
        var employee =await _mediator.Send(new GetEmployeeByEmailQuery(email));
        return Ok(employee);
    }
}در این کنترلر با استفاده از [ApiVersion(&quot;1.0&quot;)] مشخص کردیم که کنترلر ما از نسخه یک پشتیبانی خواهد کرد، همچنین در endpoint خود با اسفاده از کتابخانه mediatR اطلاعات مربوط به کارمند را دریافت میکنیم.برای آشنایی بیشتر با الگوی mediator میتوانید به این مقاله  و ویدیو مراجعه کنید.با اجرای برنامه خود میتوانیم با ارائه api-version در query string به endpoint دسترسی داشته باشیم.روش 2: استفاده از Media/Headerاین روش به ما این اجازه را میدهد تا URI خود را تمیز نگه داریم، بدین صورت که ما فقط مقادیر header را اضافه خواهیم کرد. بدین ترتیب مقدار X-Version را برابر 1 قرار میدهیم و آن را در Header ایجاد میکنیم.اگر نسخه Endpoint با نسخه ارسالی client یکسان نباشد پیام خطا دریافت خواهیم کرد، به عنوان مثال اجازه دهید نسخه endpoint بالا را به 2.0 تغییر دهیم.روش 3: به روز رسانی پارامتر Accept در headerروش سوم استفاده از پارامتر Accept میباشد، بدین صورت میتوان مقدار آن را به روز رسانی کرد و نسخه 2 مربوط به دریافت اطالاعات کارمند (GetEmployeeByEmail) را دریافت کرد.مقدار accept باید برابر با application/json;ver=2.0 باشد.روش 4: استفاده از URI versioningاستفاده از URI versionion یکی از متداولترین روش ها برای پیاده‌سازی میباشد، به این دلیل که به سادگی قابل خواندن است.بدین صورت باید کنترلر خود را به‌صورت زیر پیاده‌سازی کنیم، برای این منظور تنها کافی است که route مربوط به کنترلر را تغییر دهیم.[ApiController]
[Route(&amp;quotapi/v{version:apiVersion}&amp;quot)]
[ApiVersion(&amp;quot1.0&amp;quot)]
public class EmployeeController:ControllerBase
{
    private readonly IMediator _mediator;

    public EmployeeController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task&lt;IActionResult&gt; AddEmployee(AddEmployeeInput employee)
    {
        var id =await _mediator.Send(new AddEmployeeCommand(employee.FirstName,employee.LastName,employee.Email));
        return Ok(id);
    }
    
    [HttpGet]
    [Route(&amp;quotGetEmployeeById&amp;quot)]
   
    public async Task&lt;IActionResult&gt; GetEmployeeById( long id)
    {
        var employee =await _mediator.Send(new GetEmployeeByIdQuery(id));
        return Ok(employee);
    }
    
    [HttpGet]
    [Route(&amp;quotGetEmployeeByEmail&amp;quot)]
    [ApiVersion(&amp;quot2.0&amp;quot)]
    public async Task&lt;IActionResult&gt; GetEmployeeByEmail( string email)
    {
        var employee =await _mediator.Send(new GetEmployeeByEmailQuery(email));
        return Ok(employee);
    }
}همانطور که می بینید ما تنها route را تغییر دادیم، برای ارسال درخواست از client کافیست تا نسخه مورد نیاز را در URI ارسال کنیم.در آدرس بالا ما تنها نسخه مربوط به API را مشخص کردیم، اما برای درخواست GetEmployeeById که از نسخه 1 استفاده میکند باید URI خود را به نسخه 1 تغییر دهیم.خلاصهدر این مقاله توانستیم از نسخه بندی API استفاده کنیم، مزایای مربوط به این ویژگی را مطالعه کردیم و از روش های متفاوت برای پیاده‌سازی استفاده کردیم.برای مشاهده آموزش های بیشتر به این لینک مراجعه کنید.ممنونم بابت لایک، کامنت و دنبال کردن من، باعث میشه با قدرت بیشتری ادامه بدم :)از کدنویسی لذت ببرید!!!</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Thu, 03 Aug 2023 20:35:14 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده سازی Integration test با docker در NET 7.</title>
                <link>https://virgool.io/@v.cheshmy/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-integration-test-%D8%A8%D8%A7-docker-%D8%AF%D8%B1-net-7-p7f0tmmqtlbu</link>
                <description>مشاهده ویدیو این مقاله.یکی از بزرگترین دغدغه های دنیای برنامه نویسی و تولید نرمافزار پیادهسازی تست صحیح می باشد.بسیاری از برنامه نویسان بر این تفکر هستند که پیادهسازی تست خارج از چرخه تولید نرمافزار میباشد، در صورتی که این پیادهسازی همراه و موازی با تولید نرمافزار است.در این مقاله میخواهم در مورد پیادهسازی صحیح Integration test توضیحاتی ارائه دهم، راههای متفاوتی برای استفاده از پایگاه داده و انجام تست ها وجود دارند، راههای همچون in memory database، استفاده از docker و موارد دیگر.اما قبل از ارائه توضیحات در خصوص پیادهسازی تست اجازه دهید تفاوت بین in memory database و همچنین استفاده از پایگاه داده عملیاتی را شرح دهم.مزایا و معایب in memory databaseیک in memory database دارای مزایا و هم معایب متفاوتی می باشد. تجزیه و تحلیل مزایا و معایب آن به تعیین اینکه در چه مواردی باید از آن استفاده کرد، کمک میکند.مزایاسرعت پردازش: بارزترین مزیت هنگام استفاده از این نوع پایگاه داده، سرعت عملیات خواندن و نوشتن است. از آنجایی که پایگاه داده مبتنی بر دیسک نیست، تکنیک های بهینهسازی ، مانند پردازش موازی، امکانپذیر است.استفاده  Real-timeقابلیت اطمینان: این نوع پایگاه داده ها افزایش حجم کار را مدیریت میکنند. بازیابی اطلاعات به جای میلی ثانیه، نانوثانیه طول می کشد، که تفاوت زیادی در افزایش ناگهانی ترافیک ایجاد می کند.معایبخطر از دست دادن اطلاعات: از آنجایی که این نوع پایگاه داده از حافظه فرار استفاده می کنند، ذخیرهسازی آن موقتی است و این پایگاه داده خطر از دست دادن تمام داده ها را دارد و به اقدامات ایمنی بیشتری نیاز دارد.هزینه بالا: RAM گرانتر از دیسک های ذخیرهسازی است، با توجه به اینکه پایگاه داده به ذخیرهسازی داده های حافظه متکی است، هزینه های کلی مربوط به پایگاه های داده درون حافظه بیشتر از هزینه های یک پایگاه داده سنتی روی دیسک است.پیچیدگی معماری: پایگاه داده بر روی RAM قرار دارد که به طور خودکار RAM کمتری را برای سایر عملیات در دسترس قرار می دهد. یک راه حل، پیادهسازی محاسبات شبکه ای است که پیچیدگی معماری را افزایش می دهد.برای پیادهسازی In memory Database در NET. میتوانید به این ویدیو مراجعه کنید.پیادهسازی integration test توسط پایگاه داده واقعیراه دیگر برای پیادهسازی تست خود، استفاده از یک پایگاه داده واقعی است، ما میتوانیم با استفاده از test container پایگاه داده های خود را در زمان تست ایجاد کنیم و سپس تمامی تست خود را در آن پایگاه داده انجام دهیم.پیش نیازبرای اجرای این پروژه نیاز دارید تا Docker desktop در سیستم شما نصب باشد، برای دانلود آن میتوانید به این لینک مراجعه کنید.قدم 1: ایجاد پروژهیک پروژه class library به نام IT.Test.Integration ایجاد میکنیم.قدم 2: نصب پکیج های مورد نیازبرای ایجاد پایگاه داده و ایجاد image و همچنین container نیاز داریم تا package های زیر را نصب کنیم.همچنین در این مقاله برای تست از xUnit استفاده میکنیم.FluentAssertions
Microsoft.AspNetCore.Mvc.Testing
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.NET.Test.Sdk
Testcontainers --version 2.4.0
xunitقدم 3: پیکربندی Docker و پایگاه دادهحال زمان آن رسیده است تا پیکربندی تست خود را انجام دهیم، برای این منظور ما به یک factory نیاز داریم تا API خود را در آن ثبت کنیم.بدین منظور یک کلاس به نام CustomItApiFactory ایجاد میکنیم، این کلاس  generic می باشد:  https://gist.github.com/VahidCh013/9718e0daf070d2eb5d6eb1c4bb284db2 همانطور که مشاهده می کنید این کلاس از دو کلاس WebApplicationFactory و IAsyncLifetime ارث بری کرده است.کلاس WebApplicationFactory یک factory است که برای راهاندازی برانامه ما در حافظه مورد استفاده قرار میگیرد تا بتوانیم تست خود را انجام در یک بازه زمانی انجام دهیم.کلاس IAsyncLifetime، دارای یک طول عمر است که در لحظه شروع، اقدام به ایجاد شی و در نهایت در لحظه نهایی اقدام به dispose کردن شی میکند.حال نوبت آن فرارسیده تا نیازمندی های یک docker container را پیکر بندی کنیم، لازم به ذکر است که من در این مقاله از پایگاه داده SQL Server استفاده خواهم کرد.ابتدا اجازه دهید پیکربندی و نصب پایگاه داده SQL Server را انجام دهیم.private MsSqlTestcontainer? DatabaseTestContainer { get; set; }
public CustomItApiFactory()
{
    SetupDatabaseTestContainer();
}
private void SetupDatabaseTestContainer()
{
    DatabaseTestContainer = new TestcontainersBuilder&lt;MsSqlTestcontainer&gt;()
        .WithDatabase(new MsSqlTestcontainerConfiguration
        {
            Password = &amp;quotDevl!Fe013&amp;quot,
            Database = Guid.NewGuid().ToString()
        })
        .WithImage(&amp;quotmcr.microsoft.com/mssql/server:2022-latest&amp;quot)
        .WithCleanUp(true)
        .WithName($&amp;quotIntegrationTestDatabaseServer-{Guid.NewGuid().ToString()}&amp;quot)
        .Build();
}همانطور که ملاحظه میکنید، از MsSqlTestcontainer برای ایجاد یه پایگاه داده SQL Server استفاده کردیم، و سپس تنظیمات مربوط به این پایگاه داده را در کلاس SetupDatabaseTestContainer انجام دادیم، شما میتوانید از نسخه های مختلف image پایگاه داده SQL Server استفاده کنید، لازم به ذکر است شما میتوانید از پایگاه داده های دیگر نیز برای تست خود استفاده کنید و محدود به  SQL Server نیست.سپس نیاز دارین تا webhost خود را نیز پیکربندی کنیم، بدین منظور بایدآن را override کنیم.private string DatabaseConnectionString =&gt; $&amp;quot{DatabaseTestContainer?.ConnectionString} TrustServerCertificate=True; Encrypt=False;&amp;quot
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
    builder.ConfigureTestServices(services =&gt;
    {
        services.RemoveDbContext&lt;ItDbContext&gt;();
        services.AddDbContext&lt;ItDbContext&gt;(options =&gt; { options.UseSqlServer(DatabaseConnectionString); });
    });
    // Add database migration and update
    var migrationsAssemblyName = typeof(ItDbContext).Assembly.GetName().Name;
    var optionsBuilder = new DbContextOptionsBuilder&lt;ItDbContext&gt;()
        .UseSqlServer(DatabaseConnectionString, x =&gt; x.MigrationsAssembly(migrationsAssemblyName));
    using var dbContext = new ItDbContext(optionsBuilder.Options);
    dbContext.Database.Migrate();
}در این قسمت ابتدا اگر شی dbContext وجود داشته باشید آن را حذف میکنیم و سپس از طریق AddDbContext آن را ثبت میکنیم.سپس نیاز داریم تا عملیات migration پایگاه داده خود را نیز انجام دهیم تا مطابق یک پایگاه داده محیط عملیاتی تمامی جداول، Stored procedure های احتمالی و... را در پایگاه داده خود بهصورت خودکار ایجاد کنیم.اگر در برنامه خود تنظیمات خاصی مانند دریافت اطلاعات از APIهای دیگر داریم میتوانیم از طریق IConfigurationRoot به آن دسترسی پیدا کنیم.private IConfigurationRoot Configuration { get; set; }
private void LoadConfiguration()
{
    var configBuilder = new ConfigurationBuilder()
        .SetBasePath(Path.Combine(AppContext.BaseDirectory));
    Configuration = configBuilder.Build();
}تا اینجا تمامی پیکربندی های لازم برای تست API فراهم شده است، در انتها نیازی داریم تا طول عمر آن را نیز پیکربندی کنیم.public async Task InitializeAsync() =&gt; await DatabaseTestContainer?.StartAsync();
public new async Task DisposeAsync() =&gt;await DatabaseTestContainer.StopAsync();فایل CustomItApiFactory در نهایت به شکل زیر خواهد بودpublic class CustomItApiFactory&lt;T&gt; : WebApplicationFactory&lt;T&gt;, IAsyncLifetime
where T:class
{
    private MsSqlTestcontainer? DatabaseTestContainer { get; set; }
    private IConfigurationRoot Configuration { get; set; }
    private string DatabaseConnectionString =&gt; $&amp;quot{DatabaseTestContainer?.ConnectionString} TrustServerCertificate=True; Encrypt=False;&amp;quot
    public CustomItApiFactory()
    {
        SetupDatabaseTestContainer();
        LoadConfiguration();
    }
    private void LoadConfiguration()
    {
        var configBuilder = new ConfigurationBuilder()
            .SetBasePath(Path.Combine(AppContext.BaseDirectory));
        Configuration = configBuilder.Build();
    }
    
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =&gt;
        {
            services.RemoveDbContext&lt;ItDbContext&gt;();
            services.AddDbContext&lt;ItDbContext&gt;(options =&gt; { options.UseSqlServer(DatabaseConnectionString); });
        });
        // Add database migration and update
        var migrationsAssemblyName = typeof(ItDbContext).Assembly.GetName().Name;
        var optionsBuilder = new DbContextOptionsBuilder&lt;ItDbContext&gt;()
            .UseSqlServer(DatabaseConnectionString, x =&gt; x.MigrationsAssembly(migrationsAssemblyName));
        using var dbContext = new ItDbContext(optionsBuilder.Options);
        dbContext.Database.Migrate();
    }
    
    private void SetupDatabaseTestContainer()
    {
        DatabaseTestContainer = new TestcontainersBuilder&lt;MsSqlTestcontainer&gt;()
            .WithDatabase(new MsSqlTestcontainerConfiguration
            {
                Password = &amp;quotDevl!Fe013&amp;quot,
                Database = Guid.NewGuid().ToString()
            })
            .WithImage(&amp;quotmcr.microsoft.com/mssql/server:2022-latest&amp;quot)
            .WithCleanUp(true)
            .WithName($&amp;quotIntegrationTestDatabaseServer-{Guid.NewGuid().ToString()}&amp;quot)
            .Build();
    }
    public async Task InitializeAsync() =&gt; await DatabaseTestContainer?.StartAsync();

    public new async Task DisposeAsync() =&gt;await DatabaseTestContainer.StopAsync();
}قدم 4: تست APIحال نوبت آن فرارسیده است تا API و Endpoint های خود را تست کنیم.بدین منظور یک کلاس به نام EmployeeTests ایجاد میکنیم.public class EmployeeTests:IClassFixture&lt;CustomItApiFactory&lt;IApiMarker&gt;&gt;
{
}این کلاس از یک IClassFixture ارث بری میکند و سپس factory خود را به عنوان یک پارامتر  به آن ارسال میکنیم، کلاس CustomItApiFactory دارای یک پارامتر است تا API خود را به آن ارسال کنیم.بدین منظور شما نیاز دارید تا یک Marker در API خود ایجاد کنید و سپس آن را در  CustomItApiFactory ثبت کنید.public interface IApiMarker
{
    
}همانطور که میبینید IApiMarker یک interface خالی است که تنها به اسمبلی API ما اشاره میکند.تمامی موارد مربوط به پیکربندی ما انجام شده است و حالا میتوانیم تست خود را انجام دهیم.بدین منظور API توسعه داده شده من دارای یک Endpoint برای ایجاد یک Employee می باشد.[Fact]
public async Task Create_An_Employee_Should_Be_Successful()
{
    var request = new HttpRequestMessage(HttpMethod.Post, &amp;quot/Employee&amp;quot);
    var addEmployeeDto = new AddEmployeeInput(&amp;quotvahid&amp;quot, &amp;quotcheshmy&amp;quot, &amp;quotv.cheshmi@gmail.com&amp;quot);
    var json = JsonSerializer.Serialize(addEmployeeDto);
    var content = new StringContent(json, Encoding.UTF8, &amp;quotapplication/json&amp;quot);
    request.Content = content;
    var response=await _httpClient.SendAsync(request);
    response.StatusCode.Should().Be(HttpStatusCode.OK);
}کد بالا یک تست برای ایجاد یک Employee را نشان میدهد، این متد از طریق HttpClient اطلاعات مورد نیاز را به endpoint ما ارسال می کند و در نهایت نتیجه این endpoint بازگردانده میشود.در نهایت تست ما به شکل زیر خواهد بود.using System.Net;
using System.Text;
using IT.API;
using IT.API.Dto;
using System.Text.Json;
using FluentAssertions;
using Xunit;

namespace IT.Test.Integration.Employees;

public class EmployeeTests:IClassFixture&lt;CustomItApiFactory&lt;IApiMarker&gt;&gt;
{
    private readonly HttpClient _httpClient;

    public EmployeeTests(CustomItApiFactory&lt;IApiMarker&gt; factory)
    {
        _httpClient = factory.CreateClient();
    }

    [Fact]
    public async Task Create_An_Employee_Should_Be_Successful()
    {

        var request = new HttpRequestMessage(HttpMethod.Post, $&amp;quot/Employee&amp;quot);
        var addEmployeeDto = new AddEmployeeInput(&amp;quotvahid&amp;quot, &amp;quotcheshmy&amp;quot, &amp;quotvahid.cheshmy@myDomain.com&amp;quot);
        var json = JsonSerializer.Serialize(addEmployeeDto);
        var content = new StringContent(json, Encoding.UTF8, &amp;quotapplication/json&amp;quot);
        request.Content = content;
        var response=await _httpClient.SendAsync(request);
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }
}قدم 5: اجرای تست حال که تمامی موارد مربوط به تست ما پیادهسازی شده است می توانیم تست خود را اجرا کنیم. با اجرای تست خود باید یک پایگاه داده در docker شما ایجاد شود، تست مربوطه انجام شود و در نهایت پایگاه داده dispose شود.خلاصهدر این مقاله توانستیم با استفاده از Docker container یک integration test در پایگاه داده عملیاتی را ایجاد کنیم، همچنین پیکربندی docker و پایگاه داده را انجام دادیم و تفاوت میان in memory database را نیز مطالعه کردیم.برای مشاهده آموزش های بیشتر به این لینک مراجعه کنید.ممنونم بابت لایک، کامنت و دنبال کردن من، باعث میشه با قدرت بیشتری ادامه بدم :)از کدنویسی لذت ببرید!!!</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Mon, 31 Jul 2023 22:07:50 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده سازی background task  با استفاده از Hangfire در NET 7.</title>
                <link>https://virgool.io/@v.cheshmy/hangfire-wnkl9pdasp9o</link>
                <description>قبل از اینکه برویم سراغ استفاده از Hangfire بیاید تعریفی از background job ها داشته باشیم، این نوع Job ها برای در صف قرار دادن برخی فرایندها مورد استفاده قرار میگیرند که در پس زمینه اجرا می شوند.به عنوان مثال اجرای task های طولانی مدت بدون اینکه کاربر نهایی منتظر اتمام آن باشد، یا دریافت یک گزارش از برنامه که برای تولید این گزارش زمان زیادی نیاز داشته باشیم.تعریف Hangfireیکی از پکیج های open-source که میتوان استفاده کرد Hangfire میباشد. این پکیچ به‌صورت چند نخی (multi-thread) است و به سادگی قابل ارتقا می باشد، همچنین به دلیل ساختار خوبی که دارد به شما عملکرد بسیار قدرتمندی ارائه می دهد.دلایل استفاده از Hangfireبه‌صورت کلی برای انجام task های پس زمینه مانند ارسال ایمیل، import کردن فایل ها، ویدیوها، نگهداری پایگاه داده و ... را در اختیار شما قرار می دهد.داشبورد و پایگاه داده Hangfire از طریق داشبورد Hangfire میتوانیم تمامی job های در حال اجرا یا زمانبندی شده را که توسط hangfire client ایجاد شده است را مشاهده کنیم، همچنین این در داشبورد میتوانید تمامی خطاها، job هایی که باید دوباره اجرا شوند، و آنهایی که در صف اجرا هستند را مشاهده کرد.یکی دیگر از مزایای استفاده از این داشبورد اجرای دستی job  ها هستند.انواع تایپهای job ها در Hangfireدر حال حاضر 6 نوع تایپ برای اجرای Job ها در hangfire وجود دارند:جاب Fire-and-Forgetاین نوع job ها فقط یکبار بعد از ایجاد اجرا میشوند و سپس از چرخه ی اجرا برای همیشه خارج میشوند.جاب Delayedاین نوع job ها تنها یکبار اجرا می شوند، اما نه بعد از ایجاد آن، بلکه در یک دوره زمانی مشخص.جاب Recurringاین نوع job چندین بار در طول زمانی متفاوت اجرا می شوند، به عنوان مثال روزی یکبار، یا هر ساعت و...جاب Continuationsاین نوع job زمانی اجرا میشوند که جاب پدر (parent job) به اتمام رسیده باشد.همچنین دو نوع دیگر job ها وجود دارند اما در نسخه open-source ارائه نمی شوند و نیاز به تهیه لایسنس مربوط به آن هستین، این دو نوع  Batches و Batch Continuations هستند.جاب Batches این نوع جاب همانطور که از نام آن پیداست، گروهی از جاب ها را شامل میشوند که به به‌صورت خودکار قابل اجرا هستند.جاب Batch Continuationsاین نوع جاب زمانی اجرا میشوند که جاب پدر (parent job) به اتمام رسیده باشد.توسعه Hangfire در ASP.NET Coreقدم 1بعد از تعاریف Hangfire نوبت آن  است که آن را پیاده‌سازی کنیم، برای این منظور نیاز به ایجاد یک پروژه ASP.NET Core Web API داریم.بدین منظور در Visual Studio 2022 یک پروژه ایجاد میکنیم.قدم 2نام پروژه را به همراه مقصد آن مشخص میکنیم.قدم 3فریم ورک و همچنین نوع اجرا شدن آن را مشخص میکنیم، در اینجا فریم ورک را NET 7. انتخاب میکنیم.قدم 4بعد از ایجاد پروژه اقدام به نصب پکیج های مربوط به Hangfire میکنیم.Hangfire.AspNetCore
Hangfire.SqlServer
Microsoft.Data.SqlClientقدم 5حال نیاز داریم که تایپهای مختلفی که در بالا اشاره کردم را در یک کنترلر پیاده‌سازی کنیم.using Microsoft.AspNetCore.Mvc;
namespace HangfireDemo.Controllers;
[ApiController]
public class HomeController : Controller
{
}جاب FireAndForget[HttpGet]
[Route(&amp;quotFireAndForgetJob&amp;quot)]
public string FireAndForgetJob()
{
    var jobId = 
        BackgroundJob.Enqueue(() =&gt; Console.WriteLine(&amp;quotHello world - Fire and Forget Job&amp;quot));
    return $&amp;quotJob ID: {jobId}, Hello world - Fire and Forget Job&amp;quot
}برای تعریف FireAndForgetJob باید از متد Enqueue استفاده کنیم سپس بعد از ایجاد جاب، بلافاصله اجرا خواهد شد.جاب Delayed[HttpGet]
[Route(&amp;quotDelayedJob&amp;quot)]
public string DelayedJob()
{
      var jobId = 
        BackgroundJob.Schedule(() =&gt; Console.WriteLine(&amp;quotHello world - Delayed Job&amp;quot), TimeSpan.FromSeconds(30));
    return $&amp;quotJob ID: {jobId}, Hello world - Delayed Job&amp;quot
}برای تعریف DelayedJobباید از متد Schedule استفاده کنیم سپس بعد از ایجاد جاب،در بازه زمانی مشخص اجرا خواهد شد، در این مثال 30 ثانیه پس از ایجاد Job آن اجرا خواهد شد. جاب Continuous[HttpGet]
[Route(&amp;quotContinuousJob&amp;quot)]
public string ContinuousJob()
{
    var parentJobId = 
        BackgroundJob.Enqueue(() =&gt; Console.WriteLine(&amp;quotHello world -  Fire and Forget Job&amp;quot));
       BackgroundJob
        .ContinueJobWith(parentJobId, () =&gt; Console.WriteLine(&amp;quotHello world - continuous Job&amp;quot));
    return &amp;quotWelcome user in continuous Job Demo!&amp;quot
}در این نوع جاب ابتدا از طریق متد Enqueue یک جاب را ایجاد کردیم، سپس پس از اجرا شدن آن از طریق متد ContinueJobWith، با شناسه مربوط به جاب پدر جاب فرزند را اجرا میکنیم.جاب Recurring[HttpGet]
[Route(&amp;quotRecurringJob&amp;quot)]
public string RecurringJobs()
{
    RecurringJob
        .AddOrUpdate(() =&gt; Console.WriteLine(&amp;quotHello world - Recurring Job&amp;quot),
            Cron.Weekly);
    return &amp;quotHello world - Recurring Job &amp;quot
}این جاب به‌صورت دوره ای اجرا خواهد شد، بدین منظور از متد AddOrUpdate استفاده میکنیم، به عنوان مثال این جاب به‌صورت هفتگی اجرا خواهد شد.قدم 6در این قدم تنظیمات مربوط به پایگاه داده را در appsettings.json پیکربندی میکنیم.{
  &amp;quotLogging&amp;quot: {
    &amp;quotLogLevel&amp;quot: {
      &amp;quotDefault&amp;quot: &amp;quotInformation&amp;quot,
      &amp;quotMicrosoft.AspNetCore&amp;quot: &amp;quotWarning&amp;quot
    }
  },
  &amp;quotAllowedHosts&amp;quot: &amp;quot*&amp;quot,
  &amp;quotConnectionStrings&amp;quot: {
    &amp;quotHangfireConnection&amp;quot: &amp;quotServer=localhost;Database=dashboard;Integrated Security=true;MultipleActiveResultSets=true;TrustServerCertificate=Yes;&amp;quot
  }
}قدم 7حال نوبت به آن رسیده که پیکربندی Hangfire را در pipeline و middleware خود انجام دهیم، بدین منظور باید پیکربندی مربوط به پایگاه داده، ثبت کردن سرویس hangfire و همچنین پیکربندی UI را انجام دهیم.builder.Services.AddHangfire(x =&gt;
{
    x.UseSqlServerStorage(builder.Configuration[&amp;quotConnectionStrings:HangfireConnection&amp;quot]);
    x.UseSimpleAssemblyNameTypeSerializer();
    x.UseRecommendedSerializerSettings();
});در کد بالا تنظیمات مربوط به پایگاه داده را پیکربندی کردیم.builder.Services.AddHangfireServer();حال سرویس مربوط به سرور hangfire را ثبت میکنیم، این سرویس به صورت Transient ثبت خواهد شد.و در نهایت برای دسترسی به داشبورد و فراهم کردن UI  از کد زیر استفاده میکنیم.app.UseHangfireDashboard(&amp;quot/dashboard&amp;quot);فایل program.cs در نهایت شبیه به زیر خواهد بود.using Hangfire;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddHangfire(x =&gt;
{
    x.UseSqlServerStorage(builder.Configuration[&amp;quotConnectionStrings:HangfireConnection&amp;quot]);
    x.UseSimpleAssemblyNameTypeSerializer();
    x.UseRecommendedSerializerSettings();
});
builder.Services.AddHangfireServer();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();
app.UseHangfireDashboard(&amp;quot/dashboard&amp;quot);
app.Run();قدم 8قبل از اجرای برنامه اجازه دهید تا فایل launchSetting.json را به صورا زید بروز رسانی کنیم تا برنامه ما در پورت 5001 اجرا شود.&amp;quothttps&amp;quot: {
  &amp;quotcommandName&amp;quot: &amp;quotProject&amp;quot,
  &amp;quotdotnetRunMessages&amp;quot: true,
  &amp;quotlaunchBrowser&amp;quot: true,
  &amp;quotlaunchUrl&amp;quot: &amp;quotswagger&amp;quot,
  &amp;quotapplicationUrl&amp;quot: &amp;quothttps://localhost:5001;http://localhost:5000&amp;quot,
  &amp;quotenvironmentVariables&amp;quot: {
    &amp;quotASPNETCORE_ENVIRONMENT&amp;quot: &amp;quotDevelopment&amp;quot
  }
},قدم 9زمان آن فرارسیده است تا برنامه خود را اجرا کنیم.بعد از اجرای برنامه، پنجره swagger باز خواهد شد و شما تمامی endpoint های API خود را در آن مشاهد خواهید کرد.همچنین جداول پایگاه داده ایجاد شده نیز به شکل زیر خواهند بود.قدم 10حال زمان آن رسیده است تا endpoint های API خود را اجرا کنیم، برای این منظور در صفحه Swagger تمامی endpoint ها را یک به یک اجرا میکنیم و سپس به داشبورد hangfire مراجعه میکنیم.داشبورد تمامی جاب های در حال اجرا یا زمانبندی شده را نمایش می دهد، همنچنین می توانیم اجرای جاب ها، خطاهای موجود در جاب ها و همچنین جابهای زمانبندی شده را نیز در این داشبورد مشاهده کنیم.خلاصهدر این مقاله نگاهی کردیم به پیاده‌سازی Hangfire در NET 7. و همچنین نحوه ایجاد background job ها.مشاهده ویدیو این مقالهاز کد نویسی لذت ببرید!</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Sat, 29 Jul 2023 12:58:02 +0330</pubDate>
            </item>
                    <item>
                <title>متد های Send و Publish در MediatR (C#)</title>
                <link>https://virgool.io/@v.cheshmy/%D9%85%D8%AA%D8%AF-%D9%87%D8%A7%DB%8C-send-%D9%88-publish-%D8%AF%D8%B1-mediatr-c-nq4qjl52axcn</link>
                <description>در مقاله قبلی به طور مفصلی در مورد استفاده MediatR در #C صحبت کردم، اگر موفق به مطالعه آن نشدید به این لینک مراجعه کنید.در این مقاله، میخواهم در مورد بهینه‌سازی این الگو صحبت کنم.اما قبل از آن اجازه دهید در خصوص دو متد Send و Publish صحبت کنم.متد Sendمتد Send ارتباط بین اجزا را با استفاده از command و query تسهیل می بخشد. این متد کلیدی برای decouple کردن و همچنین جداسازی نگرانی ها (Separation of concerns) میباشد که منجر به افزایش انعطاف پذیری و نگهداری میشود.در مقاله قبل اشاره کردم که برای ایجاد message و ارسال به handler نیاز داریم تا در controller خود با استفاده از متد send این عملیات را انجام دهیم.به عنوان مثال:[HttpGet]
[Route(&amp;quotGetAllBooks&amp;quot)]
public async Task&lt;IActionResult&gt; GetAllBooks()
{
    var response=await _mediator.Send(new GetAllBooksQuery());
    return Ok(response);
}متد Publishمتد Publish به عنوان مکانیزم ارتباطی one-way مورد استفاده قرار میگیرد، که به شما اجازه میدهد تا پیامها را به چندین subscriber ارسال کنید.برای درک این موضوع به این مثال توجه کنید، فرض کنید شما طراحی Domain Driver Design را برای نرم‌افزار خود در پیش گرفته اید ، در این نرم‌افزار بعد از اینکه کاربری در سایت شما ثبت نام میکند  شما با استفاده از Event sourcing ایمیلی را به ثبت نام کننده ارسال خواهید کرد، بدین منظور می توانید از publisher برای ارسال ایمیل استفاده کنید.public class UserCreatedEvent : INotification
{
     public int Id { get; set; }
     public string UserName { get; set; } = string.Empty;
}public class UserCreatedEventHandler : INotificationHandler&lt;UserCreatedEvent&gt;
{
     private readonly IUserRepository _userRepository;
     private readonly INotificationFactory _notificationFactory;
    private readonly IMapper _mapper;
     public UserCreatedEventHandler(IUserRepository userRepository, INotificationFactory notificationFactory,IMapper mapper;)
     {
          _userRepository = userRepository;
          _notificationFactory= notificationFactory;
         _mapper=mapper
     }

     public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
     {
        var userDetails=_userRepository.FindById(notification.id);
        var userCreatedDto=_mapper.Map&lt;UserCreatedDto&gt;(userDetails);
        var notificationServiceSender=_notificationFactory.CreateEmailSender();
        await notificationServiceSender.ExecuteSender(userCreatedDto);
     }
}متد Handler اطلاعات مربوط به کاربر ثبت شده را توسط userRepository دریافت خواهد کرد و سپس یک متغیر DTO با استفاده از mapper ایجاد میکند، سپس با استفاده از الگوی factory methos یک سرویس ارسال ایمیل ایجاد میکند و در نهایت اطلاعات را به ایمیل کاربر ارسال میکند.حال زمان آن فرا رسیده که از این notification استفاده کنیم:await _mediator.Publish(notification);</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Sun, 23 Jul 2023 13:17:20 +0330</pubDate>
            </item>
                    <item>
                <title>کاهش وابستگی ها با استفاده از MediatR در7 NET.</title>
                <link>https://virgool.io/@v.cheshmy/%DA%A9%D8%A7%D9%87%D8%B4-%D9%88%D8%A7%D8%A8%D8%B3%D8%AA%DA%AF%DB%8C-%D9%87%D8%A7-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-mediatr-%D8%AF%D8%B17-net-core-s9qluurpysvi</link>
                <description>MediatRمعرفیالگو یا کتابخانه MediatR برای کاهش وابستگی ها بین object ها استفاده میشوند، به‌طور کلی این کتابخانه برای این توسعه داده شده است تا دو الگوی معماری نرم‌افزار یعنی CQRS و الگوی Mediator را ساده کند.بیایید ابتدا نگاهی به این دو الگو بیندازیم.الگوی CQRSاین الگو به معنای جدا سازی مسئولیت های نوشتن و خواندن است (Command Query Responsibility Segregation)، به‌طور کلی هدف این الگو جدا سازی عملیات های خواندن و نوشتن به دو مدل متفاوت است.اگر به الگوی های CRUD دقت کنیم، عموما ما واسطهای کاربری داریم که مسئول انجام دادن این عملیاتها بر روی یک پایگاه داده هستند، به عبارت دیگر تمامی این عملیات توسط یک واسط صورت میگیرد.اما اجازه دهید یک سؤال مطذح کنم، بین تمامی این 4 عملیات کدام عملیات بیشتر در طول عمر یک نرم‌افزار مورد استفاده قرار میگیرد؟به طور قطع عملیات خواندنالگوی CQRS این عنلیات ها را به دو مدل جدا میکند، یک عملیات برای Query ها که به عنوان R شناسایی میشود، و دیگیری برای دستورها که با CUD شناسایی میشود.شکل زیر نمایش می دهد که این الگو چگونه مورد استفاده قرار میگیرد.CQRSهمانطور که مشاهده می کنید،  نرم‌افزار ما به سادگی به دو مدل Query  و Command تقسیم شده است.الگوی Mediatorاین الگو به سادگی توضیف میکند که یک object کپسوله شده است چگونه  با دیگر object ها تعامل برقرار میکند. به عبارت دیگر، به جای اینکه دو یا چند object که به هم وابستگی داشته باشند، تنها یک واسطه مسئول برقراری این ارتباط و تعاملات خواهد بود، که به این واسطه Mediator گفته می شود.الگوی Mediatorهمانطور که در شکل بالا مشاهده میکنید، سرویس های ما یک پیام را به Mediator ارسال میکنند، و سپس Mediator آنها را از Handler ها تقاضا میکند.در این الگو هیچ وابستگی بین سرویس ها و Handler ها وجود ندارد و تعامل این دو با استفاده از Mediator صورت میگیرد.دلیل اینکه الگوی Mediator کاربرد موثری دارد این است که از الگوی inversion of control پیروی میکند که خود باعث فعال سازی loose coupling خواهد شد، بدین ترتیب وابستگی ها کاهش میبایند و کدها و تست های شما را ساده‌تر خواهد کرد.در تصویر قبلی دیدیم که چگونه سرویس‌ها هیچ وابستگی مستقیمی ندارند و تولیدکننده پیام‌ها نمی‌داند چه کسی یا چند چیز قرار است آن را مدیریت کند. این الگو بسیار شبیه به نحوه عملکرد یک واسطه پیام در الگوی &quot;publish/subscribe&quot; است. بدین ترتیب که اگر نیاز باشد تا یک Handler دیگر اضافه کنیم بدون اینکه تغییری در سرویس ها داشته باشیم.اکنون که این دو مدل را بررسی کردیم، نوبت آن است که با استفاده از کتابخانه MediatR این دو مدل را توسعه دهیم.استفاده از کتابخانه MediatRهمانطور که قبلا گفتم الگو یا کتابخانه MediatR برای کاهش وابستگی ها بین object ها استفاده میشوند.توسعه MediatR در ASP.NET Core APIبرای توسعه این کتابخانه نیاز به یک برنامه ASP.NET Core API داریم، برای دسترسی به کدهای این مقاله میتوانید آن را از Github من استفاده کنید.ابتدا نیاز داریم تا وابستگی های مربوط به برنامه را از طریق Nuget یا Package Manager Console نصب کنیم:PM&gt; install-package MediatRدر نسخه های قبل نیاز بود تا برای تزریق وابستگی ها پکیج MediatR.Extensions.Microsoft.DependencyInjectionرا نیز نصب کنیم، اما در نسخه 12 به بعد  نیازی به نصب این پکیج نیست.بعد از نصب این پکیج، فایل csproj. شما چیزی شبیه این باید باشد:&lt;Project Sdk=&amp;quotMicrosoft.NET.Sdk.Web&amp;quot&gt;
    &lt;PropertyGroup&gt;
        &lt;TargetFramework&gt;net7.0&lt;/TargetFramework&gt;
        &lt;Nullable&gt;enable&lt;/Nullable&gt;
        &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
    &lt;/PropertyGroup&gt;

    &lt;ItemGroup&gt;
        &lt;PackageReference Include=&amp;quotMediatR&amp;quot Version=&amp;quot12.1.1&amp;quot /&gt;
        &lt;PackageReference Include=&amp;quotMicrosoft.AspNetCore.OpenApi&amp;quot Version=&amp;quot7.0.7&amp;quot/&gt;
        &lt;PackageReference Include=&amp;quotSwashbuckle.AspNetCore&amp;quot Version=&amp;quot6.5.0&amp;quot/&gt;
    &lt;/ItemGroup&gt;
&lt;/Project&gt;اجازه دهید تا فایل lunchSettings را نیز بروز رسانی کنیم:{
  &amp;quotprofiles&amp;quot: {
    &amp;quothttps&amp;quot: {
      &amp;quotcommandName&amp;quot: &amp;quotProject&amp;quot,
      &amp;quotdotnetRunMessages&amp;quot: true,
      &amp;quotlaunchBrowser&amp;quot: false,
      &amp;quotapplicationUrl&amp;quot: &amp;quothttps://localhost:5001;http://localhost:5000&amp;quot,
      &amp;quotenvironmentVariables&amp;quot: {
        &amp;quotASPNETCORE_ENVIRONMENT&amp;quot: &amp;quotDevelopment&amp;quot
      }
    }
  }
}افزودن سرویس MediatR در فایل program.csحال که پکیج خود را نصب کردیم، نیاز است تا تزریق وابستگی مروبط به سرویس MediatR را ثبت کنیم، برای این کار در فایل program.cs از کد زیر استفاده کنید:builder.Services
    .AddMediatR(cfg =&gt; cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));افزودن Controllerبعد از ثبت سرویس مروبط به MediatR اجازه دهید کنترلر جدیدی را اضافه کنیم و از MediatR استفاده کنیم.در پوشه مربوط به Controllers یک کنترلر جدید به نام BookController اضافه کنید، و کدهای زیر را به آن اضافه کنید.private readonly IMediator _mediator;
public BookController(IMediator mediator)  =&gt; _mediator = mediator;در نهایت BookController شما باید شبیه به کد زیر باید:using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace MP.API.Controllers;

[ApiController]
[Route(&amp;quot[controller]&amp;quot)]
public class BookController:ControllerBase
{
    private readonly IMediator _mediator;
    public BookController(IMediator mediator)  =&gt; _mediator = mediator;
}واسط IMediatR به شما این اجازه را میدهد تا پیام های خود را به MediatR ارسال کنیم، که در ادامه به Handler مروبطه ارسال خواهد شد.پیاده‌سازی Data base یا Data storeدر دنیای واقعی برای ذخیره اطلاعا ما به پایگاه داده نیاز داریم، اما در این مقاله برای تسهیل درک این الگو از یک fake class استفاده کرده ام.کلاس Bookpublic class Book
{
    public long Id { get; init; }
    public string Name { get; init; }
}این کلاس قطعا پراپرتی ها بیشتری نسبت به کد بالا دارد، اما برای ساده‌سازی تنها به Id و نام کلاس بسنده کرده ام.حال بیایید fake data store را اضافه کنیم.public class FakeDataStore
{
    private static readonly List&lt;Book&gt; Books = new()
    {
        new Book {Id = 1,Name=&amp;quotC#&amp;quot},
        new Book {Id = 2,Name=&amp;quotASP.NET core&amp;quot},
        new Book {Id = 3,Name=&amp;quotSQL Server&amp;quot},
    };

    public async Task&lt;Book&gt; AddBook(Book book)
    {
        Books.Add(book);
        return await Task.FromResult(book);
    }
    
    public async Task&lt;IEnumerable&lt;Book&gt;&gt; GetAllBooks()
        =&gt;await Task.FromResult(Books);
}بعد از افزودن fake data store نوبت به آن میرسد که آن را در pipleline اضافه کنیم، بدین ترتیب در فایل program.cs کد زیر را اضافه می کنیم:builder.Services.AddSingleton&lt;FakeDataStore&gt;();تا به اینجا datastore ما نیز توسعه داده شده است، حال نوبت آن است تا از الگوی CQRS استفاده کنیم.جدا سازی Commands و Queriesهمانطور که قبلا گفتم، هدف CQRS جداسازی خواندن و نوشتن است، بدین منظور باید در برنامه خود این عملیا را جدا کنیم. بدین ترتیب نیاز دارین تا دو پوشه به نام های Commands و Queries به پروژه خود اضافه کنیم.در شکل بالا به‌صورت ضمنی فرایند های خواندن و نوشتن را جدا کردیم، حال نوبت آن است تا کدها و Handlerمربوط به آنها را پیاده‌سازی کنیم.ابتدا اجازه دهید تا از Queries شروع کنیم، با مراجه به Fake data store در میابیم که یک متد به نام GetAllBooks اضافه کرده ایم، بدین منظور برای تکمیل فرایند Query نیاز به یک فایل record و یک کلاس داریم.بدین ترتیب یک فایل record به نام GetAllBooksQuery اضافه میکنیم و کد زیر را نیز به آن اضافه میکنیم.public record GetAllBooksQuery():IRequest&lt;IEnumerable&lt;Book&gt;&gt;;بدین ترتیب ما یک رکورد به نام GetAllBooksQuery اضافه کردیم که از IRequest ارث بری میکند و مجموعه ای از کتابها را درخواست میکند.توجه داشته باشید، با این کد ما فقط پیام مربوط به درخواست را توسعه داده ایم اما هنوز Handler مربوط به این درخواست را توسعه نداده این، به عبارتی دیگر یک Handler باید ایجاد کنیم که مسئول رسیدگی به درخواست ما باشد و سپس اطاعات مورد نیاز را از Data store واکشی و سپس از طریق MediatR به Controller باز گرداند.پس اجازه بیایید Handler مربوط به این درخواست را نیز اضافه کنیم، بدین ترتیب یک کلاس به نام GetAllBooksQueryHanlder اضافه کنیم و کدهای زیر را نیز اضافه میکنیم.public class GetAllBooksQueryHandler : IRequestHandler&lt;GetAllBooksQuery, IEnumerable&lt;Book&gt;&gt;
{
    private readonly FakeDataStore _fakeDataStore;

    public GetAllBooksQueryHandler(FakeDataStore fakeDataStore)
    {
        _fakeDataStore = fakeDataStore;
    }

    public async Task&lt;IEnumerable&lt;Book&gt;&gt; Handle(GetAllBooksQuery request, CancellationToken cancellationToken)
        =&gt; await _fakeDataStore.GetAllBooks();
}بیایید به این کلاس بیندازیم، همانطور که مشاهده میکنید IRequestHandler که از نوع generic میباشد دارای دو پارامتر میباشد، پارامتر اول درخواستی که باید پردازش شود و پارامتر دوم پاسخ مربوط به handler میباشد.در نهایت متد Handler درخواست را به سرویس  fake data source میدهد و تمامی کتابها را در غالب یک مجموعه باز میگرداند.قبل از اینکه به سراغ controller برویم اجازه دهید تا command مربوطه را نیز توسعه دهیم، برای این منظور مانند عملیات Query نیاز به یک رکورد و همچنین یک کلاس داریم.بدین ترتیب یک فایل record به نام AddBookCommand اضافه میکنیم و کد زیر را نیز به آن اضافه میکنیم.public record AddBookCommand(long Id,string Name):IRequest&lt;Book&gt;;قبل از توسعه Hanlder مربوط به این رکورد نیاز میدانم دو مورد را توضیح دهم:1- اگر دقت کرده باشی انتهای درکورد های تعریف شده با Query و Command تفیکیک شده اند، دلیل اینکار درک بهتر این درخواست است.2- شاید سؤال پیش بیاید که دلیل بازگرداندن دوباره Book در این درخواست چیست؟ در دنیای واقعی شما نیازی به ارسال Id ندارید و در پشت صحنه ORM شما وظیفه تولید Id به عهده میگیرد، بدین ترتیب گاهی ممکن است شما نیاز به بازگرداندن Id و دیگر پارامترهای مربوط به کلاس باشید که در این صورت میتوانید از DTO ها برای انجام این کار استفاده کنید.حال به سراغ Handler مربوط به Command برویم و آن را توسعه دهیم.public class AddBookCommandHandler : IRequestHandler&lt;AddBookCommand, Book&gt;
{
    private readonly FakeDataStore _fakeDataStore;

    public AddBookCommandHandler(FakeDataStore fakeDataStore)
    {
        _fakeDataStore = fakeDataStore;
    }

    public async Task&lt;Book&gt; Handle(AddBookCommand request, CancellationToken cancellationToken)
    {
        var book = new Book()
            {Id = request.Id,Name = request.Name};
        return await _fakeDataStore.AddBook(book);
    }
}پس از توسعه Command و Query باید سرویس های مربوط به book شکل زیر باشند:توسعه Controller و تستقدم آخر، توسعه Controller مربوط به Book میباشد و در نهایت ارسال پیام با استفاده از MediatR و پردازش آن توسط handler، بدین ترتیب کدهای مربوط به BookController را اضافه میکنیم:[ApiController]
[Route(&amp;quot[controller]&amp;quot)]
public class BookController : ControllerBase
{
    private readonly IMediator _mediator;
    public BookController(IMediator mediator) =&gt; _mediator = mediator;

    [HttpGet]
    [Route(&amp;quotGetAllBooks&amp;quot)]
    public async Task&lt;IActionResult&gt; GetAllBooks()
    {
        var response=await _mediator.Send(new GetAllBooksQuery());
        return Ok(response);
    }
    
    [HttpPost]
    [Route(&amp;quotAddBook&amp;quot)]
    public async Task&lt;IActionResult&gt; GetAllBooks([FromBody] Book book)
    {
        var response=await _mediator.Send(new AddBookCommand(book.Id,book.Name));
        return Ok(response);
    }
}حال برنامه را اجرا میکنیم و با استفاده از Postman، میتوانیم Endpoint خود را تست کنیم.تست GetAllBooksهمانطور که در ابتدای مقاله فایل lunchSettings را ویرایش کردیم، برنامه ما در https://localhost:5001 اجرا خواهد شد، بدین ترتیب برای تست GetAllBooks باید Url ما به شکل زیر باشد.https://localhost:5001/Book/GetAllBooksدر نهایت خروجی مربوط به این endpoint به شکل زیر خواهد بود:تست AddBookحال زمان آن رسیده است تا یک کتاب به data store خود اضافه کنیم، بدین منظور باید یک درخواست به شکل زیر ارسال کنیم:بعد از ارسال درخواست، دوباره GetAllBooks را فراخوانی میکنیم تا مطمئن شویم کتاب جدید اضافه شده باشد:نتیجهدر این مقاله، در خصوص نحوه استفاده از MediatR با استفاده از الگوی CQRS و ASP.NET Core API صحبت کردم، متوجه شدیم که درخواست و Handler ها به چه صورتی انجام می شوند.امیدوارم که از این مقاله لذت برده باشید.مشاهده ویدیوی این مقالهاز کد نویسی لذت ببرید!</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Sun, 16 Jul 2023 16:08:14 +0330</pubDate>
            </item>
                    <item>
                <title>بررسی واحد ساختاری در NET.</title>
                <link>https://virgool.io/@v.cheshmy/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D9%88%D8%A7%D8%AD%D8%AF-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1%DB%8C-%D8%AF%D8%B1-net-bivhbic0blns</link>
                <description>قبل از اینکه مقاله رو شروع کنم، شما میتوانید نسخه ویدیویی کامل این مقاله را در این لینک مشاهده کنید.فهرست موضوعی این مقاله:- Base Class Library (BCL)، - Common Language Runtime (CLR) - Common Intermediate Language (CIL) - .NET assemblyقبل از آنکه بخواهم در مورد فهرست فوق صحبت کنم، بیایید نگاهی اجمالی به Framework Class Library (FCL)  بیندازیم که در آن می‌توانید بسیاری از کلاس‌ها، عملکردها و سایر خدمات را ببینید.Framework Class Library(FCL)معرفی بلاک های ساختمانی دات نت (CLR، CTS و CLS) بررسی CLR (Common Language Runtime)از دید یک برنامه نویس، دات نت می تواند یک محیط زمان اجرا (runtime) و یک کتابخانه کلاس پایه جامع (base class library) باشد.زمان اجرا به زمان اجرای زبان مشترک (CLR) اشاره میکند. هدف اصلی CLR بارگذاری، مکان یابی و مدیریت اشیاء دات نت از طرف برنامه نویس است. این بدان معناست که CLR همیشه این عملیات را در پس زمینه انجام می دهد و برنامه نویس در این فرآیندها مداخله ای ندارد. همچنین، CLR از مدیریت حافظه، میزبانی برنامه، برخی بررسی های امنیتی و هماهنگی thread ها مراقبت می کند.بررسی CTS (Common Type System)میتوان گفت CTS تمام انواع داده ها و تمام ساختارهای مرتبط پشتیبانی شده توسط زمان اجرا را توصیف می کند، جزئیات نحوه نمایش آنها در فرمت دات نت متادیتا را توضیح می دهد و همچنین نحوه تعامل entity با یکدیگر را مشخص می کند. به عبارت دیگر، ما چندین زبان برنامه نویسی داریم و هر زبانی تعریف نوع داده ای خاص خود را دارد و برای زبان های دیگر قابل درک نیست، اما CLR می تواند تمام انواع داده ها را درک و به یک الگوی مشترک تبدیل کند.به عنوان مثال، سی شارپ دارای نوع داده int و VB.NET دارای نوع داده integer است. بنابراین پس از کامپایل، این نوع داده ها به ساختار Int32 در CTS تبدیل می شود.بررسی CLS (Common Language Specification)باید گفت CLS زیرمجموعه‌ای از CTS است، مجموعه‌ای از قوانین و محدودیت‌هایی را تعریف می‌کند که هر زبان باید از آنها پیروی کند که تحت چارچوبNET. قابل اجرا هستند.به عنوان مثال تصور کنید برنامه ای را با #C و دیگری را با VB.NET توسعه دهید، در سی شارپ هر عبارت باید با یک نقطه ویرگول خاتمه یابد،, ولی در VB.NET انتهای کد شما به این شکل نیست.بنابراین این قواعد از زبانی به زبان دیگر متفاوت است اما CLR می تواند syntax تمام زبان ها را درک کند زیرا در دات نت هر زبان پس از کامپایل به کد MSIL تبدیل می شود و کد MSIL مشخصات زبان CLR است.بررسی Base Class Library (BCL)مخفف Base class library که با نام Class Library (CL) نیز شناخته می شود. BCL زیرمجموعه ای از کتابخانه کلاس Framework (FCL) است. Class library مجموعه ای از کتابخانه های قابل استفاده مجدد است که با CLR ادغام شده اند. کتابخانه کلاس پایه، کلاس ها و typeهایی را ارائه می دهد که در انجام عملیات روزانه مفید هستند، به عنوان مثال. سر و کار با رشته ها، انواع primitive ها ، اتصال پایگاه داده و عملیات IO.این کتابخانه نوعی را تعریف می کند که می توان از آنها برای ساخت هر نوع نرم افزاری استفاده کرد.شما می توانید از ASP.NET برای توسعه وب سایت ها، از REST برای ساخت سیستم های توزیع شده و از WPF برای ساخت برنامه های رابط کاربری گرافیکی دسکتاپ و غیره استفاده کنید. همچنین، کتابخانه های کلاس پایه انواعی را برای تعامل با دایرکتوری و سیستم فایل در یک کامپیوتر را تعیین میکند و ارتباط با پایگاه های داده رابطه ای (از طریق ADO.NET) و غیرهرا نیز ارائه می دهد.Base class libraryبررسی Managed and Unmanaged Codeاکنون که با تعریف BCL و CLR آشنا شدید، در مورد کدهای مدیریت شده (managed code) و مدیریت نشده (unmanaged code) صحبت خواهم کرد.کدهایی که توسط خود runtime در زبان برنامه نویسی #C و... نوشته میوشند و توسط CLR قابل درک هستند را کدهای مدیریت شده میگویند، اما به کدهایی که خارج از Runtime توسعه داده شده اند را کدهای مدیریت نشده میگویند.همانطور که  اشاره کردم، پلت فرم دات نت می تواند بر روی انواع سیستم عامل ها اجرا شود. می توان یک برنامه #C را بر روی سیستم عامل ویندوز با استفاده از ویژوال استودیو توسعه داد و برنامه را بر روی یک ماشین macOS  اجرا کرد. همچنین می توانید با استفاده از Xamarin Studio یک اپلیکیشن سی شارپ بر روی سیستم عامل لینوکس بسازید و برنامه را روی ویندوز، macOS و ... اجرا کنید. بررسی NET assemblies.صرف نظر از اینکه کدام زبان دات نت را برای توسعه انتخاب کرده اید، پس از کامپایل برنامه شما دو نوع اسمبلی وجود خواهد داشت، Executing files *.exe و Dynamic Link Library *.dll.اما وقتی برنامه دات نت خود را کامپایل می کنیم، به کد باینری اجرایی تبدیل نمی شود، بلکه به یک کد میانی به نام MSIL یا IL تبدیل می شود که با CLR قابل درک است. MSIL یک کد مستقل از سیستم عامل و H/W است. زمانی که برنامه باید اجرا شود، این کد MSIL یا میانی به کد اجرایی باینری تبدیل می شود که به آن کد بومی می گویند.همه مجموعه‌های دات‌نت شامل تعریف انواع، اطلاعات نسخه‌سازی برای نوع متا داده و مانیفست هستند و در این مقاله قصد دارم بیشتر در مورد آن صحبت کنم.اگر کلاسی به نام Customer دارید، نوع متادیتا جزئیاتی مانند کلاس پایه مشتری را توصیف می‌کند، مشخص می‌کند کدام رابط‌ها توسط کلاس مشتری پیاده‌سازی می‌شوند، و همچنین اطلاعاتی درباره هر عضو در کلاس مشتری ارائه می‌دهد.کتابخانه CIL و متادیتای Type، خود نیز با استفاده از metadata توصیف می‌شوند که manifestنامیده می‌شود. manifestحاوی اطلاعاتی در مورد نسخه فعلی اسمبلی و لیستی از همه مجموعه‌های ارجاع شده خارجی است که برای اجرای صحیح برنامه لازم هستند. بنابراین در مقاله زیر، من قصد دارم از ابزاری برای بررسی نوع، metadata و manifest یک اسمبلی استفاده کنم.نقش  CIL در NET.برای درک CIL به کد سی شارپ زیر که یک ماشین حساب را مدل می کند نگاهی بیندازید.using System;  
 
namespace Calculator  
{  
 
    class Program  
    {  
        static void Main(string[] args)  
        {  
            Calc cl=new Calc();  
            Console.WriteLine(&amp;quot13+31={0}&amp;quot,cl.AddNumbers(13,31));  
            Console.ReadLine();  
        }  
    }  
    //Calculator class which has a method Addnumbers  
    class Calc  
    {  
        public int AddNumbers(int x, int y)  
        {  
            return x + y;  
        }  
    }  
}همانطور که می بینید ما دو کلاس داریم:کلاس Program: که نقطه ورود برنامه استکلاس Calc: که دارای متدی به نام Addnumbers و جمع دو عدد است و نتیجه جمع اعداد را برمی گرداند.پس از کامپایل کردن برنامه با استفاده از کامپایلر C# (Calculator.exe)، در نهایت یک فایل *.exe که حاوی یک manifest ، metadata و دستورالعمل های CIL است را مشاهده خواهید کرد.حالا بیایید از ildasm.exe برای بررسی فایل Calculator.exe خود استفاده کنیم. فایل ildasm.exe را می توانید در C:\Program Files (x86)\Microsoft SDKs\Windows\[windows version ]\Bin پیدا کنید.ویژوال استودیو را باز کنیداز منو Tools گزینه  Tools external را انتخاب کنید.روی دکمه افزودن کلیک کنید، عنوان را وارد کنید (عنوان من ILDASM است)، در کادر متنی Command مسیر ildasm.exe را وارد کنید و در جعبه متن Argument فقط روی دکمه فلش سمت راست کلیک کنید و (TargetPath)$ را انتخاب کنید.و در نهایت تغییرات خود را اعمال و ذخیره کنید.سوال؟ ildasm.exe چه چیزی را ارائه می دهد؟سه چیز اساسی را به شما نشان خواهد داد:لیستی از تمام کلاس ها و متدها در اسمبلی شمامحتویات assembly manifestکد IL برای هر روش پیاده‌سازی شده در اسمبلیبرای باز کردن اسمبلی مربوطه فقط به منوی tools بروید، ILDASM در این منو ظاهر می شود و شما میتوانید آن را باز کنید.بنابراین، اگر می‌خواهید اطلاعاتی درباره برنامه خود پیدا کنید، ildasm را در منوی Tools باز کنید و کادر گفتگوی ildasm.exe را مشاهده خواهید کرد.همانطور که می بینید اطلاعاتی در مورد نوع اسمبلی، مانیفست و ابرداده وجود دارد.اگر Calculator.Calc را باز کنیم، متدهای AddNumbers خواهید دید. با دوبار کلیک کردن روی آن می توانیم اطلاعات مربوط به این متد را در شکل زیر ببینیم.به نوع داده int32 توجه کنید، همانطور که به یاد دارید در اوایل این مقاله در مورد CTS صحبت کردم که در آن همه انواع داده ها پس از کامپایل برای CTS قابل درک خواهند بود. int در #C و یا Integer در VB به صورت int32 در CTS کامپایل می‌شوند.بنابراین همانطور که می بینید متد AddNumbers با استفاده از CIL مانند تصویر بالا نشان داده شده است.مزایای CILاکنون که متوجه شدید CIL دقیقاً چه کاری انجام می دهد، سوال مطرح میشود که چرا لازم است کد خود را در CIL کامپایل کنید؟ همانطور که قبلا اشاره کردم، ممکن است شما در پروژه جاری خود از زبانهای برنامه نویسی مختلفی در NET. مانند #C و VB استفاده کنید، ولی در نهایت این زبان ها باید به شکل قابل فهمی در CIL کامپایل شوند و با هم تعامل داشته باشند.نقش NET Type Metadata.این اسمبلی حاوی Metadataهایی مانند کلاس، struct و … است. پس چگونه این اطلاعات استخراج می شود؟ توسط برنامه نویس یا کامپایلر؟ خوشبختانه تمامی این فرایند ها توسط کامپایلر صورت میگیرد و نیازی به مداخله برنامه نویس نیست.برای نشان دادن قالب متادیتا، بیایید نگاهی به متادیتا پروژه خود بیاندازیم که توسط متد AddNumbers ایجاد شده است. برای انجام این کار، ideas را در منوی ابزار خود باز کنید و پس از باز شدن کادر گفتگو، کافیست ctl+m را فشار دهید تا متادیتای پروژه خود را مشاهده کنید.همانطور که در تصویر بالا می بینید، ما یک TypeDef #2 داریم که اطلاعاتی در مورد پروژه ارائه می دهد، برای مثال TypeDefName به کلاس Calc در پروژه Calculator اشاره دارد و همچنین دو متد در کلاس Calc وجود دارد.متد AddNumbers که دو پارامتر x و y را می گیردمتد .ctor که سازنده پیش فرض کلاس Calc است و به طور خودکار توسط دات نت تولید می شود.نقش Assembly Manifestحاوی متادیتایی است که خود اسمبلی را توصیف می کند، همانطور که قبلاً اشاره کردم به آن Manifest گفته می شود.اطلاعات Manifest تمام اطلاعات مجموعه های خارجی مانند شماره نسخه اسمبلی، اطلاعات حق چاپ و غیره را مستند میکند. </description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Tue, 11 Jul 2023 18:49:34 +0330</pubDate>
            </item>
                    <item>
                <title>بهینه سازی Program.cs در NET. 7</title>
                <link>https://virgool.io/@v.cheshmy/appstart-xjvtgnxnfkrv</link>
                <description>در این مقاله قصد دارم در مورد بهینه‌سازی Program.cs صحبت کنم، همونطور که میدانید از نسخه Net 6. به بعد فایل های Program.cs و Startup.cs به نوعی با هم ترکیب شدند و یک فایل واحد به نام Program.cs ماندگار شد.در این مقاله می خواهم در مورد بهینه‌سازی این فایل صحبت کنم و اینکه چطور میتوان تا جایی که میتوانیم کدها ی مروبط به Pipeline،  سرویس ها و … را در این فایل بهینه کنیم.در نظر بگیرید، که این بهینه سازی تنها در مورد clean Code صدق میکند و در خصوص Performance شما تاثیری نخواهد داشت.به‌صورت خلاصه یک درخواست web در ASP.NET Core به شکل زیر هست.زمانی که یک درخواست ارسال می شود، Kestrel شروع به ساخت HTTP Context  می کند، و آن را به فرایند پردازش ارسال میکند که به آن Pipeline گفته میشود.هر قطعه در ASP.NET Core Application را Middleware میگوییم، Pipeline به‌صورت قطعه به قطعه فرایندها را انجام میدهد و به Pipeline بعدی ارسال میکند، در انتها Pipeline نهایی قرار دارد که به آن Endpoint گفته میشود.پس از انجام تمامی پردازش ها، Middleware ها نتیجه پردازش را به قطعه قبل از خود ارسال میکنند و در نهایت Middleware ابتدایی آن را به Kestrel  باز میگرداند.حال به بررسی Program.cs بپردازیم:بعد از ایجاد یک API، فایل Program.cs شما چیزی شبیه به قطعه کد زیر خواهد بود، این کلاس یک نمونه از Webapplication را با استفاده از الگوی Builder ایجاد میکند. Program.csسپس سرویس ها و pipeline ها را در آن ثبت میکند، یکی از مشکلاتی که ممکن است با بزرگ شدن پروژه با آن دست و پنجه نرم کنید این است که کلاس program.cs شما با گسترش پروژه ممکن است به یک God class تبدیل شود و درک و فهم آن باعث مشکل شود، برای حل این موضوع میتوان این کلاس را به دو قسمت services و pipeline تقسیم بندی کرد.بدین شکل که یک کلاس static به نام Installers ایجاد کنیم و این دو متد را در آن پیاده‌سازی کنیم.public static class Installers
{
    public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
    {
        builder.Services.AddControllers();
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();
        return builder.Build();
    }

    public static WebApplication ConfigurePipelines(this WebApplication app)
    {

        // Configure the HTTP request pipeline.
        app.UseSwagger();
        app.UseSwaggerUI();
        app.UseHttpsRedirection();
        app.UseAuthorization();
        app.MapControllers();

        return app;
     }
}و در نهایت program.cs به شکل زیر خواهد بود:var builder = WebApplication.CreateBuilder(args);
var app = builder
    .ConfigureServices()
    .ConfigurePipelines();
app.Run();همانطور که مشاهده میکنید، توانستیم کلاس Program را تا حد ممکن ساده، خوانا و بهینه کنیم و انجام هر فرایند را به متد مورد نظر ارسال کنیم، بدین صورت که قانون SRP را نیز رعایت کردیم.برای مشاهده ویدیو این مقاله به این لینک مراجعه کنید.</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Thu, 06 Jul 2023 17:57:50 +0330</pubDate>
            </item>
                    <item>
                <title>جدا سازی Endpoint و لایه Presentation  در NET 7.</title>
                <link>https://virgool.io/@v.cheshmy/%D8%AC%D8%AF%D8%A7-%D8%B3%D8%A7%D8%B2%DB%8C-endpoint-%D9%88-%D9%84%D8%A7%DB%8C%D9%87-presentation-%D8%AF%D8%B1-net-7-v9xdis5tfnbo</link>
                <description>جدا سازی Endpoint  و لایه Presentation ?شاید برای شما سوال باشد که آیا این دو با هم پیاده سازی نمیشوند؟ اگر بخواهیم به سرعت پاسخ دهیم جواب مثب است، اما با پیاده سازی این دو با هم ممکن است به یک مشکل اساسی روبرو شویم.در ادامه این مقاله خواهید دید که با جدا سازی این دو لایه از هم، میتوانید از یک مشکل بزرگ جلوگیری کنید. اخیراً در پروژه ها بیشتر روی clean architecture تمرکز کرده ام و می توانم بگویم که طرفدار واقعی این معماری هستم.پس بیایید نگاهی به این معماری بندازیم:Onion architectureدر معماری پیازی یا Onion architecture ممکن است پروژه های زیر را داشته باشیم:PresentationApplicationDomainInfrastrucureCross-Cussting edgeدر معماری بالا سعی کردم تمامی موارد مربوط به پیاده سازی معماری پیازی را بصورت خلاصه به تصویر بکشم.خب چرا لایه Presentation و Web/Api از هم جدا شدند؟ پاسخ کوتاه این است که از دسترسی مستقیم به پایگاه داده به کنترلر خودداری کنیم، اما اجازه دهید نگاهی عمیق تر به آن بیندازیم.مسئله:بیایید تصور کنیم که یک کنترلر مثل زیر داریم:public class HomeController : ControllerBase
{
    private readonly DbContext _dbContext;
    public HomeController (DbContext dbContext)
    {
      _dbContext=dbContext;
    }

    // Remaining code removed for brevity
}معمولاً کنترلرها و وب در لایه presentation پیاده‌سازی می‌شوند، اما اجازه دهید مثلای بزنم، فرض کنید توسعه‌دهنده جدیدی سعی می‌کند نمونه‌ای از DBcontext ایجاد کند در این کنترلر ایجاد کند، با ایجاد آن میتواند به پایگاه داده دسترسی داشته باشد، این نوع طراحی اصول CQRS را نقض می‌کند.راه حل:برای جلوگیری از این موضوع، می‌توانیم Web API و لایه presentation را جدا کنیم.همانطور که در شکل زیر میبینید، دو پروژه به نامهای Presentation و Api را توسعه داده ام.Web/API and Presentation layerبیایید نگاهی به کدهای داخل هر یک بیندازیم، اول از همه، اجازه دهید نگاهی به رابط IAssmblyReference.cs بیندازیم:namespace OnionArchitecture.Presentation;
/// &lt;summary&gt;
/// This empty interface will be registered by Api
/// &lt;/summary&gt;
public interface IAssemblyReference
{

}این فقط یک interface خالی است که program.cs به آن ارجاع شده است تا لایه presentation  ما را در Api ثبت کند.قدم بعدی ثبت IAssemblyReference در API است.var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers()
    .AddApplicationPart(typeof(IAssemblyReference).Assembly);
// Remaining code removed for brevityبا افزودن AddApplicationPart همه کنترلرها و غیره را از لایه Presentation در API خود ثبت می کنیم.نتیجه: با این رویکرد می توانیم به پیاده سازی CQRS دست پیدا کنیم و از دسترسی مستقیم به پایگاه داده در کنترلر اجتناب کنیم.ما فقط یک class library جدید ایجاد کردیم که شامل کنترلرها می شود و سپس آن را در لایه Web/API ثبت می کنیم.با این رویکرد با ورود یک برنامه نویس تازه کار به پروژه، از دسترسی مستقیم کنترلر به dbContext را اجتناب خواهیم کرد. </description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Tue, 04 Jul 2023 19:25:12 +0330</pubDate>
            </item>
                    <item>
                <title>تفاوت میان POCO, Value Object و DTO</title>
                <link>https://virgool.io/@v.cheshmy/%D8%AA%D9%81%D8%A7%D9%88%D8%AA-%D9%85%DB%8C%D8%A7%D9%86-poco-value-object-%D9%88-dto-mlfszdjdoy3g</link>
                <description>در این مقاله تفاوت  میان DTO، Value Object و POCO را بررسی خواهم کرد.اما ابتدا بینیم هریک مخفف چه کلماتی هستند:· DTO (Data Transfer Object)· POCO (Plain Old CLR Object)در محیط جاوا نام POCO  را به نام POJO یا Plain Old Java Object میشناسند، دلیل نامگذاری تفاوتهای ساختاری میان NET.  و جاوا میباشد.تعاریف:تعریف DTO : نمایش دهنده داده ها بدون داشتن منطقی درون خود میباشد، DTO ها معمولا برای تبادل اطلاعات بین برنامه ها و لایجه های مختلف یک برنامه استفاده میشوند.تعریف Value Object : یک عضو کامل از Domain model هستند. هر قانونی که در Entity صورت میگیرد در Value Object هم صورت میگیرد. تفاوت اصلی بین Value Object  و Entity در این است که Value Object ها هیچ Id در خود نگهداری نمیکنند. به این معنی که دو Value Object با پراپرتی های یکسان باید با هم یکسان باشند اما این قانون در Entity ها نباید یکسان باشد و دلیل آن وجود Id  در Entity  ها می باشد. Value Object ها همچنین دارای منطق میباشند و عموما برای تبادل بین داده ها در مرزهای برنامه استفاده نمی شوند.تعریف POJO یا POCO : توسط آقای مارتین فاولر در ابتدای دهه 2000 معرفی شد. هدف اصلی آن این بود که یک دامین  به راحتی میتواند بدون ارتباطات پیچیده مدل سازی شود.ارتباطات میان POCO, DTOو Value Object:آیا ارتباطی بین این اصطلاحات وجود داری؟ بله چند ارتباط وجود دارد.اول از همه، DTO و Value Object مفاهیم متفاوتی را ارائه میدهند و نمیتوانند با هم معاوضه شوند. از سویی دیگر، Value object و DTO زیر مجموعه POCO هستند.به بیان دیگر Value Object  و DTO نباید از کامپوننتهای  enterprise مانند DBCommand ارث بری کنند.اما POCOها مجموعه های عریض تری هستند که میتوانند Value Object، Entity، DTO و یا هر کلاس دیگری که در برگیرنده پیچیدگی نباشد باشند.بصورت دیگر :در این مقاله سعی کردم توضیحات مختصری در مورد ValueObject, POCO و DTO ارائه بدم و تفاوتهای اساسی آنها را با هم بررسی کردیم. </description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Sun, 23 Jan 2022 18:58:46 +0330</pubDate>
            </item>
                    <item>
                <title>Boolean blindness - Application Level Code Smell</title>
                <link>https://virgool.io/@v.cheshmy/boolean-blindness-application-level-code-smell-bulhlz7v3wqg</link>
                <description>اولین نوعی که میتوان در مورد Application Level Code Smell صحبت کرد، Boolean blindness  میباشد.این مورد به این اشاره می کند که اطلاعات بسیار کمی در مورد فانکشنها ارائه شده است که از مقادیر boolean  استفاده میکند.مسئله:کد زیر را در نظر بگیریدpublic void BookConcert(string concert, bool standing)
{
            if (standing)
            {
                   // Issue standing ticket.
            }
            else
            {
                     // Issue sitting ticket.
             }
}این متد یک نوع string برای نام کنسرت دریافت می کند و یک boolean برای اینکه شخص در محل کنسرت خواهد ایستاد یا خواهد نشست.حال کدی که برای رزرو کنسرت باید استفاده کنیم یک نگاه بکنیم.private void BooleanBlindnessConcertBooking()
{
       var booking = new ProblemCode.ConcertBooking();
       booking.BookConcert(&amp;quotSolitary Experiments&amp;quot, true);
}اگر یک برنامه نویس جدید به تیم برنامه نویسی اضافه شود و این متد را مشاهده کند، آیا فکر میکنید که آن شخص میداند که ارسال مقدار true به این متد دقیقا چه کاری انجام میدهد؟ پس میشود گفت که این برنامه نویس اصطلاحا کور شده است، زیرا نمیداند که متد دقیقا چه کاری انجام میدهد، بدین منظور یا باید از IntelliSence کمک بگیرد یا اینکه مکان این متد را پیدا کند و کدهای درون متد را بررسی کند که منظور از standing دقیقا چه چیزی می باشد! راه حل:حل این مسئله میتوانید از enum کمک بگیریم و آن را جاگزین boolean کنیم.internal enum TicketType
{
     Seated,
     Standing
}بدین صورت که enum تعریف شده دارای دو نوع میباشد، Seated  و Standing. حال بیایید دوباره نگاهی به متد BookConcert بیندازیم.internal void BookConcert(string concert, TicketType ticketType)
{
        if (ticketType == TicketType.Seated)
       {
            // Issue seated ticket.
       }
       else
       {
           // Issue standing ticket.
        }
}کد زیر نشان میدهد که چگونه می توان از مند جدید refactor شده استفاده کرد.private void ClearSightedConcertBooking()
{
       var booking = new RefactoredCode.ConcertBooking();
       booking.BookConcert(&amp;quotChrom&amp;quot, TicketType.Seated);
}حال اگر یک برنامه نویس جدید به تیم اضافه شود و نگاهی به این متد بیندازد متوجه خواهد شد که این متد عملیات رزرو را انجام میدهد.مشاهده ویدیو </description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Sun, 14 Nov 2021 17:48:29 +0330</pubDate>
            </item>
                    <item>
                <title>شناسایی Code smells</title>
                <link>https://virgool.io/@v.cheshmy/%D8%B4%D9%86%D8%A7%D8%B3%D8%A7%DB%8C%DB%8C-code-smells-ltoswjbz5ee8</link>
                <description>در صنعت، به کدهایی که دارای مشکل هستند اصطلاحا Code Smells میگویند.اینها کدهای  کامپایل، اجرا و دارای نتیجه هستند، دلیل اینکه به این کدها، کدهای مشکل دار میگویند این است که غیرقابل خواندن، پیچیده و همچنین نگهداری و توسعه را سخت میکنند و باید در اسرع وقت آنها را Refactor کرد.بصورت کلی سه نوع Code Smell ممکن است در برنامه وجود داشته باشند:1- کدهای سطح Application2- کدهای سطح کلاس3-کدهای سطح متد</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Sun, 14 Nov 2021 17:15:27 +0330</pubDate>
            </item>
                    <item>
                <title>3 ویژگی مهم جدید در C#10</title>
                <link>https://virgool.io/@v.cheshmy/3-%D9%88%DB%8C%DA%98%DA%AF%DB%8C-%D9%85%D9%87%D9%85-%D8%AC%D8%AF%DB%8C%D8%AF-%D8%AF%D8%B1-c10-jpj12k80tcms</link>
                <description>1- گسترش PropertyPattern ها:گاهی اوقات به  Property های تو در تو نیاز داریم، به معنی دیگر دسترسی به Property یک Propperty دیگر. دسترسی به این ویژگی در نسخه های قبلی #C امکان پذیر بود ولی کدی که باید نوشته میشد تا حدودی تمیز نبود.1.1 مشکل فعلی:فرض کنید برای دسترسی به Property تو در تو باید به این شکل کد نوشته میشد:if (employee is { Address: { City: &amp;quotTehran&amp;quot } })1.2ویژگی جدید C#10 :دقیقا C#10  در این مورد بهینه شده و بجای اینکه شما چندین بار از براکتها استفاده کنید میتونید کد رو بصورت زیر دهید:if (employee is { Address.City: &amp;quotTehran&amp;quot })2- استفاده از Global Using ها:شاید تابحال برای شما این سوال مطرح شده که آیا راهی برای این هست که بتوان استفاده از Using های متوالی   و تکراری را در جایی  ذخیره کرد و دیگر آن ها را در فایلهای متعدد تکرار نکرد؟باید گفت به لطف وجود نسخه جدید NET. این ویژگی اضافه شده.2.1 مشکل فعلی:اگر به مثال زیر توجه کنید، لیست Using  ها زیاد هستندusing Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; 
using Microsoft.AspNetCore.HttpsPolicy; 
using Microsoft.AspNetCore.Identity; 
using Microsoft.AspNetCore.Identity.UI; 
using Microsoft.EntityFrameworkCore; 
using Microsoft.Extensions.Configuration; 
using Microsoft.Extensions.DependencyInjection; 
using Microsoft.Extensions.Hosting; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks;2.2 ویژگی جدید C#10 :کلمه کلیدی جدیدی که در این نسخه معرفی شده استفاده از global هست. با استفاده از global، شما میتوانید از using  های که بصورت global  اضافه میشوند استفاده کنید. پیشنهاد میشود که یک فایل جداگانه ایجاد کنید و لیست using های خود را در آن import کنید، به عنوان مثال usings.csglobal using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting; 
global using Microsoft.AspNetCore.HttpsPolicy; 
global using Microsoft.AspNetCore.Identity; 
global using Microsoft.AspNetCore.Identity.UI; 
global using Microsoft.EntityFrameworkCore; 
global using Microsoft.Extensions.Configuration; 
global using Microsoft.Extensions.DependencyInjection; 
global using Microsoft.Extensions.Hosting; 
global using System; 
global using System.Collections.Generic; 
global using System.Linq; 
global using System.Threading.Tasks;3- بهینه سازی File Namespace ها:3.1 مشکل فعلی:بعد از تعریف using ها در کلاس شما، اولین چیزی که نوشته میشود تعریف namespace است. و بعد از آن تعریف کلاس شما.namespace Blog
{   
     public class Article  
     {     
          //پراپرتی ها و صفات کلاس   
     } 
}3.2 ویژگی جدید C#10 :در نسخه جدید، شما میتواند براکتها را حذف کنید و بصورت first level  از آن استفاده کنید.namespace Blog;       
 public class Article        
{               
    //پراپرتی ها و صفات کلاس         
}  </description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Wed, 10 Nov 2021 15:26:58 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده سازی DDD در NET5. (بخش دوم)</title>
                <link>https://virgool.io/@v.cheshmy/encapsulation-%D9%88-separation-of-concerns-r6btanyp8din</link>
                <description>قبل از پیاده سازی Domain Driven Design بیاید کمی در مورد Encapsulation و SoC بحث کنیم. Encapsulationنقشی است که یکپارچگی داده ها محافظت میکند، یک کلاس زمانی به درستی encapsulate شده که داده های داخلی اون را نتوان با ارسال حالتهای نامعتبر و بی ثبات تغییر داد. دو تکنیک وجود دارد که به شما برای این کار کمک میکند، پنهان سازی اطلاعات و جمع آوری داده و انجام عملیات با یکدیگر.پنهان سازی داده به شما این کمک را میکند که ویژگی ها، صفات و پراپرتی های داخلی کلاسها رو از دید Client دور کنید تا ریسک برهم زدن ویژگی های داخلی کلاس را کم کند.جمع آوری داده و عملیات به شما این امکان را میدهد تا با استفاده از یک ورودی، بتوانید تمامی کارهای کلاس را انجام دهید.Separation of Concernsیک فانون کلیدی دیگر که برای توسعه نرم افزار باید رعایت شود رعایت separation of concern می باشد، به بیان ساده تر این قانون رابطه بسیار نزدیکی به قانون Single Responsibility Principle دارد.بدین معنی که اگر کلاس شما وظیقه این را بر عهده دارد که محصولات با ارزش را شناسایی کند اما در کنار آن اطلاعات مشتری را نیز نمایش دهد، این کلاس SoC را نقض میکند.برای درک بهتر این موضوع به مثال زیر توجه کنید:public static void Main(string[] args)
{
    Product product = new Product()
    {
        Id=1,
        Name = &amp;quotSmartPhone&amp;quot,
        Code = &amp;quotGalaxy A&amp;quot
    };
    product.NoteWorthy(1);
    product.UserDetail(100);
}public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Code { get; set; }

    public void NoteWorthy(int id)
    {
        //get noteworthy products
    }

    public void UserDetail(int userId)
    {
        //get user detail
    }
}public class Course
{
    [Column(&amp;quotIsActive&amp;quot,TypeName=&amp;quotChar(1)&amp;quot)]
    public bool IsActive { get; set; }
    [Column(&amp;quotStudents_enrolled&amp;quot,TypeName=&amp;quotint&amp;quot)]
    public int NumberOfStudents { get; set; }
}کلاس Product دو قانون ذکر شده فوق را نقض میکند، طبق قانون encapsulation هر کلاس باید مسئول امور داخلی خود باشد، اما با نگاه به تایع main متوجه میشیم که این مسئولیت از کلاس product صلب شده و یا بهتر باید گفت کلاس product طوری طراحی شده که در هر جایی از برنامه client میتواند به امور داهلی کلاس تصف داشته باشد و حالت State کلاس را تغییر دهد.اما طبق قانون SoC ، هر کلاس باید تنها امور داخلی خود را انجام دهد و هر گونه تغییر رفتار در کلاس دیر قاون SoC را نقض میکند، اگر به کلاس product نگاهی بکنید به راحتی متوجه میشوید که متد UserDetail این قانون را نقض کرده است و به همین علت میتوان گفت که SoC بسیار نزدیک به SRP  می باشد.مثال دیگر که قانون SoC را نقض میکند را میتوانید در کلاس Course مشاهده کنید، در این کلاس دو موضوع وجود دارد.1- Domain Modeling2- ORM mappingراه حل: ابتدا به سراغ Encapsulation میرویم، همانطور که گفته شد هر کلاس باید مسئول امور داخلی خود باشد.public static void Main(string[] args)
{
    var product = Product.AddProduct(1, &amp;quotSmartPhone&amp;quot, &amp;quotGalaxy A&amp;quot);
    product.NoteWorthy(1);
}public class Product
    {
        public int Id { get; private set; }
        public string Name { get; private set; }
        public string Code { get; private set; }

        public Product(int id, string name, string code)
        {
           //logic to check validity
            Id = id;
            Name = name;
            Code = code;
        }
        public void NoteWorthy(int id)
        {
            //get noteworthy products
        }

        public static Product AddProduct(int id, string name, string code)
        {
            return new Product(id, name, code);
        }
}همانطور که در بالا مشاهده میکنید ، متدی به نام AddProduct نوشته ایم که یک شی از Product  را ایجاد میکند، با این تغییرات Client امکان تغییر property های کلاس Product را در کلاس Main نخواهد داشت.قدم بعدی رعایت قانون SoC می باشد و همانطور که گفته شد به لطف EF Core  میتوان Data modeling  و ORM  از از یکدیگر جدا کرد.public class Course
{
    public bool IsActive { get; set; }
    public int NumberOfStudents { get; set; }
}public class CourseConfiguration : IEntityTypeConfiguration&lt;Course&gt;
{
    public void Configure(EntityTypeBuilder&lt;Course&gt; builder)
    {
builder.Property(x =&gt; x.IsActive).HasColumnName(&amp;quotIsActive&amp;quot).HasColumnType(&amp;quotchar(1)&amp;quot);
builder.Property(x =&gt; x.NumberOfStudents).HasColumnName(&amp;quotStudents_enrolled&amp;quot).HasColumnType(&amp;quotint&amp;quot);
      }
}</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Mon, 18 Oct 2021 20:15:46 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده سازی DDD  در NET5. (بخش اول - مقدمه)</title>
                <link>https://virgool.io/@v.cheshmy/ddd-introduction-mfgsmmxmxqx4</link>
                <description>در سالهای اخیر Entity Framework پیشرفتهای چشمگیری داشته و ویژگی های بسیار زیادی رو برای پیاده سازی Domain Model  های rich  و  پیاده سازی دامینهایی با encapsulation بسیار بالا عرضه کرده.قبل از شروع این دوره پیشنهاد میکنم  تا در مورد موارد زیر تسلط نسبی داشته باشید:C#EF (Entity Framework)DDD (Domain Driven Design)اگر در نسخه های قبل سعی کرده باشین که DDD  رو با استفاده از EF پیاده سازی کنید، شاید با چالشهایی روبرو شده باشین، اما به لطف توسعه های اخیر در EF Core  میشود گفت که  پیاده سازی DDD  بسیار کاربردی تر شده است.در این مقاله خواهید آموخت که چگونه مدل خود را پیاده سازی کنید بدون اینکه به Encapsulation دامین شما آسیبی برسد، همچنین به جزئیات و قدم به قدم شروع به پیاده سازی Rich Domain Model  میکنیم و در طول مسیر خواهید دید که چگونه EF Core بصورت داخلی کار میکند و از چه ویژگی های از اون باید بهره ببرید.این مقاله به بخشهای زیر تقسیم بندی خواهد شد:1- قوانین Encapsulation و  SoC  (Separation Of Concern) و چرا این دو قانون بسیار مهم هستند.2-پیاده سازی یک برنامه و نحوه استفاده از روابط  many‑to‑one ، many-to-many , one-to-one و many-to-one3-پیاده سازی Base Entity Class4- استفاده از Lazy Loading و eager loading 5- استفاده از Backing Field  برای پنهان سازی عملیات و نحوه استفاده از اون در Collection6- استفاده از Value Object</description>
                <category>وحید چشمی</category>
                <author>وحید چشمی</author>
                <pubDate>Mon, 18 Oct 2021 18:44:26 +0330</pubDate>
            </item>
            </channel>
</rss>