مطالب
آشنایی با تست واحد و استفاده از کتابخانه Moq
تست واحد چیست؟

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


اهمیت انجام تست واحد چیست؟

درستی یک متد، مهمترین مسئله برای بررسی است و بارها مشاهده شده، استثناهایی رخ میدهند که توان تولید را به دلیل فرسایش تکراری رخداد میکاهند. نوشتن تست واحد منجر به این می‌شود چناچه بعدها تغییری در بیزنس متد ایجاد شود و ورودی و خروجی‌ها تغییر نکند، صحت این تغییر بیزنس، توسط تست بررسی مشود؛ حتی میتوان این تست‌ها را در build پروژه قرار داد و در ابتدای اجرای یک Solution تمامی تست‌ها اجرا و درستی بخش به بخش اعضا چک شوند.


شروع تست واحد:

یک پروژه‌ی ساده را داریم برای تعریف حساب‌های بانکی شامل نام مشتری، مبلغ سپرده، وضعیت و 3 متد واریز به حساب و برداشت از حساب و تغییر وضعیت حساب که به صورت زیر است:
    /// <summary>
    /// حساب بانکی
    /// </summary>
    public class Account
    {
        /// <summary>
        /// مشتری
        /// </summary>
        public string Customer { get; set; }
        /// <summary>
        /// موجودی حساب
        /// </summary>
        public float Balance { get; set; }
        /// <summary>
        /// وضعیت
        /// </summary>
        public bool Active { get; set; }

        public Account(string customer, float balance)
        {
            Customer = customer;
            Balance = balance;
            Active = true;
        }
        /// <summary>
        /// افزایش موجودی / واریز به حساب
        /// </summary>
        /// <param name="amount">مبلغ واریز</param>
        public void Credit(float amount)
        {
            if (!Active)
                throw new Exception("این حساب مسدود است.");
            if (amount < 0)
                throw new ArgumentOutOfRangeException("amount");
            Balance += amount;
        }
        /// <summary>
        /// کاهش موجودی / برداشت از حساب
        /// </summary>
        /// <param name="amount">مبلغ برداشت</param>
        public void Debit(float amount)
        {
            if (!Active)
                throw new Exception("این حساب مسدود است.");
            if (amount < 0)
                throw new ArgumentOutOfRangeException("amount");
            if (Balance < amount)
                throw new ArgumentOutOfRangeException("amount");
            Balance -= amount;
        }
        /// <summary>
        /// انسداد / رفع انسداد
        /// </summary>
        public void ChangeStateAccount()
        {
            Active = !Active;
        }
    }
تابع اصلی نیز به صورت زیر است:
    class Program
    {
        static void Main(string[] args)
        {
            var account = new Account("Ali",1000);

            account.Credit(4000);
            account.Debit(2000);
            Console.WriteLine("Current balance is ${0}", account.Balance);
            Console.ReadKey();
        }
    }
به Solution، یک پروژه از نوع تست واحد اضافه میکنیم.
در این پروژه ابتدا Reference ایی از پروژه‌ای که مورد تست هست میگیریم. سپس در کلاس تست مربوطه شروع به نوشتن متدی برای انواع تست متدهای پروژه اصلی میکنیم.
توجه داشته باشید که Data Annotation‌های بالای کلاس تست و متدهای تست، در تعیین نوع نگاه کامپایلر به این بلوک‌ها موثر است و باید این مسئله به درستی رعایت شود. همچنین در صورت نیاز میتوان از کلاس StartUp برای شروع تست استفاده کرد که عمدتا برای تعریف آن از نام ClassInit استفاده میشود و در بالای آن از [ClassInitialize] استفاده میشود.
در Library تست واحد میتوان به دو صورت چگونگی صحت عملکرد یک تست را بررسی کرد: با استفاده از Assert و با استفاده از ExpectedException، که در زیر به هر دو صورت آن میپردازیم.
    [TestClass]
    public class UnitTest
    {
        /// <summary>
        /// تعریف حساب جدید و بررسی تمامی فرآیند‌های معمول روی حساب
        /// </summary>
        [TestMethod]
        public void Create_New_Account_And_Check_The_Process()
        {
            //Arrange
            var account = new Account("Hassan", 4000);
            var account2 = new Account("Ali", 10000);
            //Act
            account.Credit(5000);
            account2.Debit(3000);
            account.ChangeStateAccount();
            account2.Active = false;
            account2.ChangeStateAccount();
            //Assert
            Assert.AreEqual(account.Balance,9000);
            Assert.AreEqual(account2.Balance,7000);
            Assert.IsTrue(account2.Active);
            Assert.AreEqual(account.Active,false);
        }
همانطور که مشاهده میشود ابتدا در قسمت Arrange، خوراک تست آماده میشود. سپس در قسمت Act، فعالیت‌هایی که زیر ذره بین تست هستند صورت می‌پذیرند و سپس در قسمت Assert درستی مقادیر با مقادیر مورد انتظار ما مطابقت داده میشوند.
برای بررسی خطاهای تعیین شده هنگام نوشتن یک متد نیز میتوان به صورت زیر عمل کرد:
        /// <summary>
        /// زمانی که کاربر بخواهد به یک حساب مسدود واریز کند باید جلوی آن گرفته شود.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof (Exception))]
        public void When_Deactive_Account_Wants_To_add_Credit_Should_Throw_Exception()
        {
            //Arrange
            var account = new Account("Hassan", 4000) {Active = false};
            //Act
            account.Credit(4000);
            //Assert
            //Assert is handled with ExpectedException
        }

        [TestMethod]
        [ExpectedException(typeof (ArgumentOutOfRangeException))]
        public void When_Customer_Wants_To_Debit_More_Than_Balance_Should_Throw_ArgumentOutOfRangeException()
        {
            //Arrange
            var account = new Account("Hassan", 4000);
            //Act
            account.Debit(5000);
            //Assert
            //Assert is handled with ArgumentOutOfRangeException
        }
همانطور که مشخص است نام متد تست باید کامل و شفاف به صورتی انتخاب شود که بیانگر رخداد درون متد تست باشد. در این متدها Assert مورد انتظار با DataAnnotation که پیش از این توضیح داده شد کنترل گردیده است و بدین صورت کار میکند که وقتی Act انجام میشود، متد بررسی می‌کند تا آن Assert رخ بدهد.


استفاده از Library Moq در تست واحد

ابتدا باید به این توضیح بپردازیم که این کتابخانه چه کاری میکند و چه امکانی را برای انجام تست واحد فراهم میکند.
در پروژه‌های بزرگ و زمانی که ارتباطات بین لایه‌ای زیادی موجود است و اصول SOLID رعایت میشود، شما در یک لایه برای ارایه فعالیت‌ها و خدمات متدهایتان با Interface‌های لایه‌های دیگر در ارتباط هستید و برای نوشتن تست واحد متدهایتان، مشکلی بزرگ دارید که نمیتوانید به این لایه‌ها دسترسی داشته باشید و ماهیت تست واحد را زیر سوال میبرید. Library Moq این امکان را به شما میدهد که از این Interface‌ها یک تصویر مجازی بسازید و همانند Snap Shot با آن کار کنید؛ بدون اینکه در لایه‌های دیگر بروید و ماهیت تست واحد را زیر سوال ببرید.
برای استفاده از متدهایی که در این Interface‌ها موجود است شما باید یک شیء از نوع Mock<> از آنها بسازید و سپس با استفاده از متد Setup به صورت مجازی متد مورد نظر را فراخوانی کنید و مقدار بازگشتی مورد انتظار را با Return معرفی کنید، سپس از آن استفاده کنید.
همچنین برای دسترسی به خود شیء از Property ایی با نام Objet از موجودیت mock شده استفاده میکنیم.
برای شناسایی بهتر اینکه از چه اینترفیس هایی باید Mock<> بسازید، میتوانید به متد سازنده کلاسی که معرف لایه ایست که برای آن تست واحد مینویسید، مراجعه کنید.
نحوه اجرای یک تست واحد با استفاده از Moq با توجه به توضیحات بالا به صورت زیر است:
پروژه مورد بررسی لایه Service برای تعریف واحد‌های سازمانی است که با الگوریتم DDD و CQRS پیاده سازی شده است.
ابتدا به Constructor خود لایه سرویس نگاه میکنیم تا بتوانید شناسایی کنید از چه Interface هایی باید Mock<> کنیم.
  public class OrganizationalService : ICommandHandler<CreateUnitTypeCommand>,
                                         ICommandHandler<DeleteUnitTypeCommand>,                                    
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IUnitTypeRepository _unitTypeRepository;
        private readonly IOrganizationUnitRepository _organizationUnitRepository;
        private readonly IOrganizationUnitDomainService _organizationUnitDomainService;

        public OrganizationalService(IUnitOfWork unitOfWork, IUnitTypeRepository unitTypeRepository, IOrganizationUnitRepository organizationUnitRepository, IOrganizationUnitDomainService organizationUnitDomainService)
        {
            _unitOfWork = unitOfWork;
            _unitTypeRepository = unitTypeRepository;
            _organizationUnitRepository = organizationUnitRepository;
            _organizationUnitDomainService = organizationUnitDomainService;
        }
مشاهده میکنید که 4 Interface استفاده شده و در متد سازنده نیز مقدار دهی شده اند. پس 4 Mock نیاز داریم. در پروژه تست به صورت زیر و در ClassInitialize عمل میکنیم.
    [TestClass]
    public class OrganizationServiceTest
    {
        private static OrganizationalService _organizationalService;
        private static Mock<IUnitTypeRepository> _mockUnitTypeRepository;
        private static Mock<IUnitOfWork> _mockUnitOfWork;
        private static Mock<IOrganizationUnitRepository> _mockOrganizationUnitRepository;
        private static Mock<IOrganizationUnitDomainService> _mockOrganizationUnitDomainService;

        [ClassInitialize]
        public static void ClassInit(TestContext context)
        {
            TestBootstrapper.ConfigureDependencies();
            _mockUnitOfWork = new Mock<IUnitOfWork>();
            _mockUnitTypeRepository = new Mock<IUnitTypeRepository>();
            _mockOrganizationUnitRepository = new Mock<IOrganizationUnitRepository>();
            _mockOrganizationUnitDomainService=new Mock<IOrganizationUnitDomainService>();
            _organizationalService = new OrganizationalService(_mockUnitOfWork.Object, _mockUnitTypeRepository.Object,  _mockOrganizationUnitRepository.Object,_mockOrganizationUnitDomainService.Object);
        }
از خود لایه سرویس با نام OrganizationService یک آبجکت میگیریم و 4 واسط دیگر به صورت Mock شده تعریف میشوند. همچنین در کلاس بارگذار از همان نوع مقدار دهی میگردند تا در اجرای تمامی متدهای تست، در دست کامپایلر باشند. همچنین برای new کردن خود سرویس از mock.obect‌ها که حاوی مقدار اصلی است استفاده می‌کنیم.
خود متد اصلی به صورت زیر است:
        /// <summary>
        /// یک نوع واحد سازمانی را حذف مینماید
        /// </summary>
        /// <param name="command"></param>
        public void Handle(DeleteUnitTypeCommand command)
        {
            var unitType = _unitTypeRepository.FindBy(command.UnitTypeId);
            if (unitType == null)
                throw new DeleteEntityNotFoundException();

            ICanDeleteUnitTypeSpecification canDeleteUnitType = new CanDeleteUnitTypeSpecification(_organizationUnitRepository);
            if (canDeleteUnitType.IsSatisfiedBy(unitType))
                throw new UnitTypeIsUnderUsingException(unitType.Title);
            _unitTypeRepository.Remove(unitType);
        }
متد‌های تست این متد نیز به صورت زیر هستند:
        /// <summary>
        /// کامند حذف نوع واحد سازمانی باید به درستی حذف کند.
        /// </summary>
        [TestMethod]
        public void DeleteUnitTypeCommand_Should_Delete_UnitType()
        {
            //Arrange
            var unitTypeId=new Guid();
            var deleteUnitTypeCommand = new DeleteUnitTypeCommand { UnitTypeId = unitTypeId };
            var unitType = new UnitType("خوشه");
            var org = new List<OrganizationUnit>();
            _mockUnitTypeRepository.Setup(d => d.FindBy(deleteUnitTypeCommand.UnitTypeId)).Returns(unitType);
            _mockUnitTypeRepository.Setup(x => x.Remove(unitType));
            _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org);
            try
            {
                //Act
                _organizationalService.Handle(deleteUnitTypeCommand);
            }
            catch (Exception ex)
            {
                //Assert
                Assert.Fail(ex.Message);
            }
        }
همانطور که مشاهده میشود ابتدا یک Guid به عنوان آی دی نوع واحد سازمانی گرفته میشود و همان آی دی برای تعریف کامند حذف به آن ارسال میشود. سپس یک نوع واحد سازمانی دلخواه تستی ساخته میشود و همچنین یک لیست خالی از واحد‌های سازمانی که برای چک شدن توسط خود متد Handle استفاده شده‌است ساخته میشود. در اینجا این متد خالی است تا شرط غلط شود و عمل حذف به درستی صورت پذیرد.
برای اعمالی که در Handle انجام میشود و متدهایی که از Interface‌ها صدا زده میشوند Setup میکنیم و آنهایی را که Return دارند به object هایی که مورد انتظار خودمان هست نسبت میدهیم.
در Setup اول میگوییم که آن Guid مربوط به "خوشه" است. در Setup بعدی برای عمل Remove کدی مینویسیم و چون عمل حذف Return ندارد میتواند، این خط به کل حذف شود! به طور کلی Setup هایی که Return ندارند میتوانند حذف شوند.
در Setup بعدی از Interface دیگر متد FindBy که قرار است چک کند این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است، در Return به آن یک لیست خالی اختصاص میدهیم تا نشان دهیم لیست خالی برگشته است.
عملیات Act را وارد Try میکنیم تا اگر به هر دلیل انجام نشد، Assert ما باشد.
دو حالت رخداد استثناء که در متد اصلی تست شده است در دو متد تست به طور جداگانه تست گردیده است:
        /// <summary>
        /// کامند حذف یک نوع واحد سازمانی باید پیش از حذف بررسی کند که این شناسه داده شده برای حذف موجود باشد.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(DeleteEntityNotFoundException))]
        public void DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitTypeId_NotExist()
        {
            //Arrange
            var unitTypeId = new Guid();
            var deleteUnitTypeCommand = new DeleteUnitTypeCommand();
            var unitType = new UnitType("خوشه");
            var org = new List<OrganizationUnit>();
            _mockUnitTypeRepository.Setup(d => d.FindBy(unitTypeId)).Returns(unitType);
            _mockUnitTypeRepository.Setup(x => x.Remove(unitType));
            _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org);

            //Act
            _organizationalService.Handle(deleteUnitTypeCommand);
        }

        /// <summary>
        /// کامند حذف یک نوع واحد سازمانی نباید اجرا شود وقتی که نوع واحد برای تعریف واحد‌های سازمان استفاده شده است.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(UnitTypeIsUnderUsingException))]
        public void DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitType_Exist_but_UsedForDefineOrganizationUnit()
        {
            //Arrange
            var unitTypeId = new Guid();
            var deleteUnitTypeCommand = new DeleteUnitTypeCommand { UnitTypeId = unitTypeId };
            var unitType = new UnitType("خوشه");
            var org = new List<OrganizationUnit>()
            {
                new OrganizationUnit("مدیریت یک", unitType, null),
                new OrganizationUnit("مدیریت دو", unitType, null)
            };
            _mockUnitTypeRepository.Setup(d => d.FindBy(deleteUnitTypeCommand.UnitTypeId)).Returns(unitType);
            _mockUnitTypeRepository.Setup(x => x.Remove(unitType));
            _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org);

            //Act
            _organizationalService.Handle(deleteUnitTypeCommand);
        }
متد DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitTypeId_NotExist همانطور که از نامش معلوم است بررسی میکند که نوع واحد سازمانی که ID آن برای حذف ارسال میشود در Database وجود دارد و اگر نباشد Exception مطلوب ما باید داده شود.
در متد DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitType_Exist_but_UsedForDefineOrganizationUnit بررسی میشود که از این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است یا نه و صحت این مورد با الگوی Specification صورت گرفته است. استثنای مطلوب ما Assert و شرط درستی این متد تست، میباشد.
پاسخ به بازخورد‌های پروژه‌ها
نشان دادن گزارش قبل از ذخیره یا چاپ
سورس پروژه رو دریافت کنید.
بعد مراجعه کنید به پوشه مثال‌های آن. در اینجا می‌تونید مثال‌های نحوه نمایش فایل گزارش تهیه شده را در برنامه‌های WinForms، WPF، سیلورلایت، وب ASP.NET و غیره ملاحظه کنید.
+ توضیحات بیشتر
نظرات مطالب
بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net
کمکی نمی‌تونم بهتون بکنم و من عموما پیغام‌هایی مانند پیغام شما رو حذف می‌کنم به این دلایل:
- نه کدی ارائه شده (کل Solution شما نیاز است برای بررسی)
- نه توضیحات کافی که چه مراحلی رو طی کردید
- نه توضیحاتی که از چه ابزاری و چه نگارشی از VS.NET دارید استفاده می‌کنید.

و توقع دارید که من از راه دور کد شما رو در VS.NET ایی که معلوم نیست چه نگارشی است دیباگ کنم، پس از عیب یابی مشخص کنم مشکل چی بوده ...

این‌ها رو هم نوشتم تا کسانی که پیغام‌هاشون حذف شده بدونند علت این امر چی بوده.
مطالب
راه اندازی دیتابیس postgresql در برنامه‌های ASP.NET Core – قسمت 2

در قسمت قبل به معرفی postgresql پرداختیم; در این قسمت قصد ایجاد و راه اندازی یک api با استفاده از دیتابیس postgresql و استفاده از تکنولوژی‌های آن را با استفاده از docker داریم.


ابتدا با استفاده از دستور زیر یک پروژه‌ی جدید asp.net core را ایجاد کنید:

dotnet new webapi --minimal -o YourDirectoryPath:\YourFolderName

سپس فایل docker-compose.yaml را به روت پروژه اضافه کنید که شامل کانفیگ‌های زیر میباشد: 

version: '3.1'

services:

  db:
    image: postgres
    container_name: db
    restart: always
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USERNAME: postgres
      POSTGRES_DB: BloggingDb
    ports:
        - "5432:5432"
    volumes:
      - postgres_data:/data/db

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
  
  pgadmin4:
    image: dpage/pgadmin4
    restart: always
    environment:
      PGADMIN_DEFAULT_EMAIL: pgadmin4@pgadmin.org
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_CONFIG_SERVER_MODE: 'False'
    ports:
      - 5050:80
    volumes:
      - pgadmin:/var/lib/pgadmin
    depends_on:
      - db

volumes:
  postgres_data:
  pgadmin:

سپس با اجرای دستور زیر در روت پروژه، سرویس‌ها را راه اندازی کنید: 

docker compose up -d


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

سرویس db

نمونه ایمیج اصلی، volume، تنظیمات connection string در آن استفاده شده است.

سرویس adminer :

https://hub.docker.com/_/adminer /

Adminer - Database management in a single PHP file

یک برنامه تحت وب مدیریت پایگاه داده ساده میباشد که ویژگی‌ها MySql را در کنار سرعت و امنیت ارائه میدهد و در آدرس http://localhost:8080 / اجرا خواهد شد.

سرویس pgadmin4 :

pgAdmin - PostgreSQL Tools

dpage/pgadmin4 - Docker Image | Docker Hub

در حال حاضر این برنامه محبوب‌ترین برنامه مدیریت پایگاه داده میباشد که ویژگی‌های پیشرفته‌ای را نیز پوشش میدهد و در آدرس http://localhost:5050 / اجرا خواهد شد. 


اکنون نوبت نوشتن کد‌ها میباشد. 

- تنظیم connection string در فایل appsettings.json:

"ConnectionStrings": {
    "BloggingContext": "Username=postgres;Password=postgres;Server=localhost;Database=BloggingDb”
}

- و همینطور پکیج‌های زیر را به برنامه خود رفرنس دهید: 

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design

- مدل‌های برنامه را در مسیر /Models ایجاد کنید: 

namespace NpgsqlAPI.Models;

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

    public int BlogId { get; set; }
    public Blog Blog { get; set; } = null!;
}

namespace NpgsqlAPI.Models;

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

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

- سپس BloggingContext را در مسیر /Data ایجاد کنید:

using Microsoft.EntityFrameworkCore;
using NpgsqlAPI.Models;

namespace NpgsqlAPI.Data;

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    {
    }
    public DbSet<Blog> Blogs => Set<Blog>();
    public DbSet<Post


- سپس اینترفیس IBlogServices را در مسیر  /Servicec/Blogs ایجاد کنید: 

using NpgsqlAPI.Models;

namespace NpgsqlAPI.Services.Blogs;
public interface IBlogServices
{
    Task<IEnumerable<Blog>> GetList();
    Task<Blog?> Get(uint id);
    Task<uint> Add(Blog obj);
    Task AddRange(Blog[] obj);
    Task Update(Blog obj);
    Task UpdateRange(Blog[] obj);
    Task Remove(uint id);
}


-  و سپس پیاده سازی آن را در فایل BlogEFServices و در کنار اینترفیس آن قرار دهید: 

using Microsoft.EntityFrameworkCore;
using NpgsqlAPI.Data;
using NpgsqlAPI.Models;

namespace NpgsqlAPI.Services.Blogs;
public sealed class BlogEFServices : IBlogServices
{
    private readonly BloggingContext _context;
    public BlogEFServices(BloggingContext context)
    {
        _context = context;
    }

    public async Task<uint> Add(Blog obj)
    {
        await _context.Blogs.AddAsync(obj);
        return (uint)await SaveChangesAsync();
    }

    public async Task AddRange(Blog[] obj)
    {
        await _context.Blogs.AddRangeAsync(obj);
        await SaveChangesAsync();
    }

    public async Task<Blog?> Get(uint id)
    {
        return await _context.Blogs.FirstOrDefaultAsync(x=>x.BlogId == id);
    }

    public async Task<IEnumerable<Blog>> GetList()
    {
       return await _context.Blogs.ToListAsync();
    }

    public async Task Remove(uint id)
    {
        var entity = await Get(id);
        _context.Blogs.Remove(entity!);
        await SaveChangesAsync();
    }

    public async Task Update(Blog obj)
    {
        _context.Blogs.Update(obj);
        await SaveChangesAsync();
    }

    public async Task UpdateRange(Blog[] obj)
    {
        _context.Blogs.UpdateRange(obj);
        await SaveChangesAsync();
    }

    private async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }
}


- اکنون  endpoint‌های api را در فایل program.cs ایجاد کنید:

using System.Data;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using NpgsqlAPI.Services.Blogs;
using NpgsqlAPI.Data;
using NpgsqlAPI.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

string connectionString = builder.Configuration.GetConnectionString("BloggingContext")!;

builder.Services.AddDbContext<BloggingContext>(options =>
        options.UseNpgsql(connectionString));

builder.Services.AddTransient<IDbConnection>(_ => new NpgsqlConnection(connectionString));

// builder.Services.AddScoped<IBlogServices, BlogDapperServices>();
// builder.Services.AddScoped<IBlogServices, BlogEFRawQueryServices>();
builder.Services.AddScoped<IBlogServices, BlogEFServices>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/blogs", async (IBlogServices service) => await service.GetList())
.WithName("GetBlogs")
.WithOpenApi();

app.MapGet("/blogs/{id}", async (IBlogServices service, uint id) => await service.Get(id))
.WithName("GetBlog")
.WithOpenApi();

app.MapPost("/blogs", async (IBlogServices service, Blog blog) => await service.Add(blog))
.WithName("AddBlog")
.WithOpenApi();

app.MapDelete("/blogs/{id}", async (IBlogServices service, uint id) => await service.Remove(id))
.WithName("RemoveBlog")
.WithOpenApi();

app.MapPut("/blogs", async (IBlogServices service, Blog blog) => await service.Update(blog))
.WithName("UpdateBlog")
.WithOpenApi();

app.MapPut("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) =>
 await service.UpdateRange(blogs))
.WithName("UpdateBulkBlog")
.WithOpenApi();

app.MapPost("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) =>
 await service.AddRange(blogs))
.WithName("AddBulkBlog")
.WithOpenApi();

app.Run();

تمامی کد‌های برنامه تا به اینجا نوشته شده‌اند. اکنون migration را پس از اطمینان از اجرا بودن داکر اجرا کنید 

dotnet ef migrations add Init
dotnet ef database update

و برنامه را اجرا و تست کنید. 


کد‌های کامل این مطلب

نظرات مطالب
بررسی بهبودهای پروسه‌ی Build در دات‌نت 8

یک نکته‌ی تکمیلی: چگونه آنالایزرهای بسیار کند را پیدا کنیم؟!

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

برای پاسخ به این سؤال، فقط کافی است پروژه را با سوئیچ bl/، کامپایل کنیم:

dotnet build /bl

خروجی آن که یک فایل باینری است به نام msbuild.binlog، توسط برنامه‌ی «MSBuild Binary and Structured Log Viewer» قابل مشاهده‌است:

پس از باز کردن این فایل در برنامه‌ی یاد شده، می‌توان ذیل قسمت Analyzer summary (مانند تصویر فوق)، زمان صرف شده‌ی توسط هر آنالایزر را مشاهده کرد و در صورت نیاز، موارد زمانبر را از پروژه حذف کرد.

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

dotnet run -p:RunAnalyzers=false
نظرات مطالب
دریافت خروجی سایت
واقعا جای تقدیر داره، نسخه CHM هم واقعا مفید بود ،بعضا مجموعه‌های اموزشی این بلاگ رو به دانشجو‌ها معرفی می‌کنم.  اگر امکان تولید این فایل PDF بر اساس برچسب‌ها بود حجم کمتری پیدا میکرد و دانلود اون برای بعضی راحت‌تر میشد.

ممنون از زحمات بی منت شما در این بلاگ
نظرات مطالب
وادار کردن خود به کامنت نوشتن
سلام
تو مطلب مربوط به لینک اول یعنی درست کردن فایل های راهنما ی CHM ، دانلود SDK مربوط به 
Visual Studio 2008 SDK Version 1.0 حدودا 95 مگابایته. ولی برای 2010 حدودا 11 مگابایت. این دوتا با هم تفاوت دارند؟
مطالب
آشنایی با NHibernate - قسمت هفتم

مدیریت بهینه‌ی سشن فکتوری

ساخت یک شیء SessionFactory بسیار پر هزینه و زمانبر است. به همین جهت لازم است که این شیء یکبار حین آغاز برنامه ایجاد شده و سپس در پایان کار برنامه تخریب شود. انجام اینکار در برنامه‌های معمولی ویندوزی (WinForms ،WPF و ...)، ساده است اما در محیط Stateless وب و برنامه‌های ASP.Net ، نیاز به راه حلی ویژه وجود خواهد داشت و تمرکز اصلی این مقاله حول مدیریت صحیح سشن فکتوری در برنامه‌های ASP.Net است.

برای پیاده سازی شیء سشن فکتوری به صورتی که یکبار در طول برنامه ایجاد شود و بارها مورد استفاده قرار گیرد باید از یکی از الگوهای معروف طراحی برنامه نویسی شیء گرا به نام Singleton Pattern استفاده کرد. پیاده سازی نمونه‌ی thread safe آن که در برنامه‌های ذاتا چند ریسمانی وب و همچنین برنامه‌های معمولی ویندوزی می‌تواند مورد استفاده قرار گیرد، در آدرس ذیل قابل مشاهده است:



از پنجمین روش ذکر شده در این مقاله جهت ایجاد یک lazy, lock-free, thread-safe singleton استفاده خواهیم کرد.

بررسی مدل برنامه

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


یک برنامه ASP.Net را آغاز کرده و ارجاعاتی را به اسمبلی‌های زیر به آن اضافه نمائید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHibernate.Linq.dll
و همچنین ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

تصویر نهایی پروژه ما به شکل زیر خواهد بود:



پروژه ما دارای یک پوشه domain ، تعریف کننده موجودیت‌های برنامه جهت تهیه نگاشت‌های لازم از روی ‌آن‌ها است. سپس یک پوشه جدید را به نام NHSessionManager به آن جهت ایجاد یک Http module مدیریت کننده سشن‌های NHibernate در برنامه اضافه خواهیم کرد.

ساختار دومین برنامه (مطابق کلاس دیاگرام فوق):

namespace NHSample3.Domain
{
public class Car
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Color { get; set; }
}
}

using System.Collections.Generic;

namespace NHSample3.Domain
{
public class Parking
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Location { get; set; }
public virtual IList<Car> Cars { get; set; }

public Parking()
{
Cars = new List<Car>();
}
}
}
مدیریت سشن فکتوری در برنامه‌های وب

در این قسمت قصد داریم Http Module ایی را جهت مدیریت سشن‌های NHibernate ایجاد نمائیم.

در ابتدا کلاس Config را در پوشه مدیریت سشن NHibernate با محتویات زیر ایجاد کنید:

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;

namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.ExposeConfiguration(
x => x.SetProperty("current_session_context_class", "managed_web")
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample3.Domain.Car).Assembly))
);
}

public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
با این کلاس در قسمت‌های قبل آشنا شده‌اید. در این کلاس با کمک امکانات Auto mapping موجود در Fluent Nhibernate (مطلب قسمت قبلی این سری آموزشی) اقدام به تهیه نگاشت‌های خودکار از کلاس‌های قرار گرفته در پوشه دومین خود خواهیم کرد (فضای نام این پوشه به دومین ختم می‌شود که در متد GetConfig مشخص است).
دو نکته جدید در متد GetConfig وجود دارد:
الف) استفاده از متد FromConnectionStringWithKey ، بجای تعریف مستقیم کانکشن استرینگ در متد مذکور که روشی است توصیه شده. به این صورت فایل وب کانفیگ ما باید دارای تعریف کلید مشخص شده در متد GetConfig به نام DbConnectionString باشد:

<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true" />
</connectionStrings>
ب) قسمت ExposeConfiguration آن نیز جدید است.
در اینجا به AutoMapper خواهیم گفت که قصد داریم از امکانات مدیریت سشن مخصوص وب فریم ورک NHibernate استفاده کنیم. فریم ورک NHibernate دارای کلاسی است به نام NHibernate.Context.ManagedWebSessionContext که جهت مدیریت سشن‌های خود در پروژه‌های وب ASP.Net پیش بینی کرده است و از این متد در Http module ایی که ایجاد خواهیم کرد جهت ردگیری سشن جاری آن کمک خواهیم گرفت.

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



سپس کلاس SingletonCore را جهت تهیه تنها و تنها یک وهله از شیء سشن فکتوری در کل برنامه ایجاد خواهیم کرد (همانطور که عنوان شده، ایده پیاده سازی این کلاس thread safe ، از مقاله معرفی شده در ابتدای بحث گرفته شده است):

using NHibernate;

namespace NHSessionManager
{
/// <summary>
/// lazy, lock-free, thread-safe singleton
/// </summary>
public class SingletonCore
{
private readonly ISessionFactory _sessionFactory;

SingletonCore()
{
_sessionFactory = Config.GetConfig().BuildSessionFactory();
}

public static SingletonCore Instance
{
get
{
return Nested.instance;
}
}

public static ISession GetCurrentSession()
{
return Instance._sessionFactory.GetCurrentSession();
}

public static ISessionFactory SessionFactory
{
get { return Instance._sessionFactory; }
}

class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly SingletonCore instance = new SingletonCore();
}
}
}
اکنون می‌توان از این Singleton object جهت تهیه یک Http Module کمک گرفت. برای این منظور کلاس SessionModule را به برنامه اضافه کنید:

using System;
using System.Web;
using NHibernate;
using NHibernate.Context;

namespace NHSessionManager
{
public class SessionModule : IHttpModule
{
public void Dispose()
{ }

public void Init(HttpApplication context)
{
if (context == null)
throw new ArgumentNullException("context");

context.BeginRequest += Application_BeginRequest;
context.EndRequest += Application_EndRequest;
}

private void Application_BeginRequest(object sender, EventArgs e)
{
ISession session = SingletonCore.SessionFactory.OpenSession();
ManagedWebSessionContext.Bind(HttpContext.Current, session);
session.BeginTransaction();
}

private void Application_EndRequest(object sender, EventArgs e)
{
ISession session = ManagedWebSessionContext.Unbind(
HttpContext.Current, SingletonCore.SessionFactory);
if (session == null) return;

try
{
if (session.Transaction != null &&
!session.Transaction.WasCommitted &&
!session.Transaction.WasRolledBack)
{
session.Transaction.Commit();
}
else
{
session.Flush();
}
}
catch (Exception)
{
session.Transaction.Rollback();
}
finally
{
if (session != null && session.IsOpen)
{
session.Close();
session.Dispose();
}
}
}
}
}
کلاس فوق کار پیاده سازی اینترفیس IHttpModule را جهت دخالت صریح در request handling pipeline برنامه ASP.Net جاری انجام می‌دهد. در این کلاس مدیریت متدهای استاندارد Application_BeginRequest و Application_EndRequest به صورت خودکار صورت می‌گیرد.
در متد Application_BeginRequest ، در ابتدای هر درخواست یک سشن جدید ایجاد و به مدیریت سشن وب NHibernate بایند می‌شود، همچنین یک تراکنش نیز آغاز می‌گردد. سپس در پایان درخواست، این انقیاد فسخ شده و تراکنش کامل می‌شود، همچنین کار پاکسازی اشیاء نیز صورت خواهد گرفت.

با توجه به این موارد، دیگر نیازی به ذکر using جهت dispose کردن سشن جاری در کدهای ما نخواهد بود، زیرا در پایان هر درخواست اینکار به صورت خودکار صورت می‌گیرد. همچنین نیازی به ذکر تراکنش نیز نمی‌باشد، چون مدیریت آن‌را خودکار کرده‌ایم.

جهت استفاده از این Http module تهیه شده باید چند سطر زیر را به وب کانفیگ برنامه اضافه کرد:

<httpModules>
<!--NHSessionManager-->
<add name="SessionModule" type="NHSessionManager.SessionModule"/>
</httpModules>
بدیهی است اگر نخواهید از Http module استفاده کنید باید این کدها را در فایل Global.asax برنامه قرار دهید.

اکنون مثالی از نحوه‌ی استفاده از امکانات فراهم شده فوق به صورت زیر می‌تواند باشد:
ابتدا کلاس ParkingContext را جهت مدیریت مطلوب‌تر LINQ to NHibernate تشکیل می‌دهیم.

using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample3.Domain;

namespace NHSample3
{
public class ParkingContext : NHibernateContext
{
public ParkingContext(ISession session)
: base(session)
{ }

public IOrderedQueryable<Car> Cars
{
get { return Session.Linq<Car>(); }
}

public IOrderedQueryable<Parking> Parkings
{
get { return Session.Linq<Parking>(); }
}
}
}
سپس در فایل Default.aspx.cs برنامه ، برای نمونه تعدادی رکورد را افزوده و نتیجه را در یک گرید ویوو نمایش خواهیم داد:

using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHSample3.Domain;
using NHSessionManager;

namespace NHSample3
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//ایجاد دیتابیس در صورت نیاز
//Config.CreateDb();

//ثبت یک سری رکورد در دیتابیس
ISession session = SingletonCore.GetCurrentSession();

Car car1 = new Car() { Name = "رنو", Color = "مشکلی" };
session.Save(car1);
Car car2 = new Car() { Name = "پژو", Color = "سفید" };
session.Save(car2);

Parking parking1 = new Parking()
{
Location = "آدرس پارکینگ مورد نظر",
Name = "پارکینگ یک",
Cars = new List<Car> { car1, car2 }
};

session.Save(parking1);

//نمایش حاصل در یک گرید ویوو
ParkingContext db = new ParkingContext(session);
var query = from x in db.Cars select new { CarName = x.Name, CarColor = x.Color };
GridView1.DataSource = query.ToList();
GridView1.DataBind();
}
}
}
مدیریت سشن فکتوری در برنامه‌های غیر وب

در برنامه‌های ویندوزی مانند WinForms ، WPF و غیره، تا زمانیکه یک فرم باز باشد، کل فرم و اشیاء مرتبط با آن به یکباره تخریب نخواهند شد، اما در یک برنامه ASP.Net جهت حفظ منابع سرور در یک محیط چند کاربره، پس از پایان نمایش یک صفحه وب، اثری از آثار اشیاء تعریف شده در کدهای آن صفحه در سرور وجود نداشته و همگی بلافاصله تخریب می‌شوند. به همین جهت بحث‌های ویژه state management در ASP.Net در اینباره مطرح است و مدیریت ویژه‌ای باید روی آن صورت گیرد که در قسمت قبل مطرح شد.
از بحث فوق، تنها استفاده از کلاس‌های Config و SingletonCore ، جهت استفاده و مدیریت بهینه‌ی سشن فکتوری در برنامه‌های ویندوزی کفایت می‌کنند.

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

ادامه دارد ....

نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
ممنون از مطالبتون.
من با VS 2013  کار می‌کنم، در قسمت "افزودن فایل tsconfig.json به پروژه" در لیست ظاهر شده، گزینه‌ی TypeScript JSON Configuration File برای من نمایش داده نمی‌شود مشکل کجاست؟