از آنجایی که درخواست Get، رایجترین نوع درخواست است، از این رو نمیتوانیم هنگام ایجاد API آن را نادیده بگیریم. پس خیلی مهم است که handle کردن این نوع درخواست را یاد بگیرید.
در پست قبلی، ما یک الگوی repository برای بدست آوردن داده از دیتابیس ایجاد کردیم.
حالا زمان آن رسیده است که از این repository، برای business logic استفاده کنیم.
ما باید تمام منطق دیتابیس را داخل کلاسهای repository نگه داریم. کنترلرها، مسئول handle کردن درخواستها، اعتبارسنجی مدل و برگشت response ها به قسمت frontend اپلیکیشن خواهند بود.
با این کار ، کنترلر های ما بیش از حد درگیر کد نخواهند بود، بنابراین خواندن و نگهداری کد نیز آسان تر می شود.
این مقاله، قسمتی از مجموعه آموزشی زیر میباشد:
- ایجاد یک دیتابیس برای پروژه
- پیاده سازی کدهای پایه
- log سفارشی در ASP.NET Core
- الگوی Repository با Entity Framework Core
- استفاده از Repository برای درخواستهای GET(همین مقاله)
- استفاده از Repository برای درخواستهای POST، PUT و DELETE
اگر می خواهید تمام آموزشهای لازم و پایه مربوط به این دوره آموزشی را ببینید ، لطفاً روی این لینک کلیک کنید: صفحه مقدمه برای این آموزش.
برای مطالعه قسمت قبلی، بر روی این لینک کلیک کنید: ایجاد پروژه NET Core WebApi. – الگوی Repository در NET Core.
این مقاله شامل قسمتهای زیر میباشد:
- کنترلرها و مسیریابی در WEB API
- در خواست GetAllOwners GET در .NET Core
- مجوزهای کد و تست نتیجه
- استفاده از DTO و AutoMapper
- درخواست GetOwnerById GET در NET Core.
- درخواست جزییات Owner
- نتیجه گیری
کنترلرها و مسیریابی در WEB API
برای ایجاد یک کنترلر، بر روی پوشه Controllers داخل پروژه اصلی، راست کلیک کرده و Add/Controller را انتخاب کنید. سپس از منو، گزینه API Controller – Empty را انتخاب کنید و نام آن را OwnerController.cs بگذارید:

کنترلر ما باید به این صورت باشد:
1 2 3 4 5 6 7 8 9 10 |
using Microsoft.AspNetCore.Mvc; namespace AccountOwnerServer.Controllers { [Route("api/[controller]")] [ApiController] public class OwnerController : ControllerBase { } } |
هر کلاس کنترلر web API از کلاس ControllerBase
abstract که تمام رفتارهای لازم را برای کلاس مشتق شده در اختیار میگذارد ارث بری میکند.
همچنین، بالای کلاس controller، میتوانیم این کد را ببینیم:
1 |
[Route("api/[controller]")] |
این کد، ارائه دهنده مسیریابی در کنترلر میباشد و ما کمی در مورد مسیریابی در Web API ها صحبت خواهیم کرد.
مسیریابی Web API، درخواستهای ورودی HTTP را به اکشن متد صحیح در داخل کنترلر Web API هدایت میکند.
دو نوع مسیریابی وجود دارد:
- مسیریابی بر اساس قرارداد
- Attribute routing
مسیریابی بر اساس قرارداد به دلیل اینکه یک قرارداد برای مسیرهای URL ایجاد میکند به این صورت نامگذاری شده است. اولین قسمت از این قانون مسیریابی، نام کنترلر و دومین قسمت، نام action method و سومین قسمت برای پارامتر اختیاری مورد استفاده قرار میگیرد. ما میتوانیم این نوع از مسیریابی را در کلاس Startup درون متد Configure پیکربندی کنیم:

Attribute routing از attribute ها برای مپ کردن مستقیم مسیرها به action method ها در کنترلر استفاده میکند. معمولا همانطور که میتوانید در کنترلر Web API مان توجه کنید، ما میتوانیم route پایه را بالای کلاس controller قرار دهیم. به طور مشابه، برای action method های مورد نظرمان نیز میتوانیم route هایشان را درست بالای خود آنها قرار دهیم.
در خواست GetAllOwners GET در NET Core.
شروع میکنیم.
اول از همه، روت پایه را از [Route(“api/[controller]”)] به [Route(“api/owner”)] تغییر میدهیم. اگرچه route اولی به خوبی کار خواهد کرد، اما با route دومی، ما دقیقا نشان میدهیم که مسیر باید به خود OwnerController اشاره کند.
حال باید اولین action method که قرار است که تمام owner ها از دیتابیس را برگرداند را ایجاد کنیم.
در اینترفیس IOwnerRepository، تعریفی برای متد GetAllOwners ایجاد کنید:
1 2 3 4 |
public interface IOwnerRepository { IEnumerable<Owner> GetAllOwners(); } |
سپس این اینترفیس را داخل کلاس OwnerRepository پیاده سازی کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
namespace Repository { public class OwnerRepository : RepositoryBase<Owner>, IOwnerRepository { public OwnerRepository(RepositoryContext repositoryContext) :base(repositoryContext) { } public IEnumerable<Owner> GetAllOwners() { return FindAll() .OrderBy(ow => ow.Name) .ToList(); } } } |
در آخر، باید تمام owner ها را با استفاده از متد GetAllOwners داخل اکشن Web API برگردانیم.
هدف action method ها داخل کنترلرهای Web API، فقط برگرداندن نتایج نیست. برگرداندن نتایج، هدف اصلی اکشن متد است، اما فقط این نیست. بلکه شما باید به status code های response های Web API نیز توجه کنید. علاوه بر این، شما باید action هایتان را با HTTP attribute ها که نوع HTTP request آن اکشن را مشخص میکند را تعیین کنید.
در آخر، OwnerController را به این صورت تغییر میدهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
using Contracts; using Microsoft.AspNetCore.Mvc; using System; namespace AccountOwnerServer.Controllers { [Route("api/owner")] [ApiController] public class OwnerController : ControllerBase { private ILoggerManager _logger; private IRepositoryWrapper _repository; public OwnerController(ILoggerManager logger, IRepositoryWrapper repository) { _logger = logger; _repository = repository; } [HttpGet] public IActionResult GetAllOwners() { try { var owners = _repository.Owner.GetAllOwners(); _logger.LogInfo($"Returned all owners from database."); return Ok(owners); } catch (Exception ex) { _logger.LogError($"Something went wrong inside GetAllOwners action: {ex.Message}"); return StatusCode(500, "Internal server error"); } } } } |
حالا اجازه بدید تا کمی این کد را شرح دهیم.
اول از همه، ما سرویسهای logger و repository را به داخل سازنده، تزریق میکنیم. سپس با مشخص کردن اکشن GetAllOwners با ویژگی [HttpGet]
، تعیین میکنیم که این اکشن یک درخواست از نوع GET میباشد. در آخر،ما از هر دو پارامتر تزریق شده برای log کردن پیغامها و دریافت داده از کلاس repository استفاده میکنیم. اینترفیس IActionResult از نوعهای مختلفی از متدها حمایت میکند که نه تنها نتیجه، بلکه status code ها را نیز Return میکند. در اینجا، متد OK، تمام owner ها و همچنین کد وضعیت 200 که به معنی OK است را برمیگرداند. اگر یک exception رخ دهد، ما خطای internal server با کد وضعیت 500 را برمیگردانیم.
به این دلیل که هیچ route attribute بالای اکشن GetAllOwners وجود ندارد، از این جهت، Route برای این اکشن، همان api/owner (http://localhost:5000/api/owner
) میباشد.
مجوزهای کد و تست نتیجه
میخواهیم به یک مورد دیگری داخل اکشن GetAllOwners اشاره کنیم. اگر به ساختار repository نگاه کنید، کلاسهای آن از کلاس abstract RepositoryBase<T>
و همچنین از اینترفیسهای مربوط به خودشان که از اینترفیس IRepositoryBase<T>
مشتق میشوند، ارث بری میکنند. با استفاده از این سلسله مراتب، با تایپ کردن ،_repository.Owner.
میتوانید متد سفارشی از کلاس OwnerRepository و همچنین تمام متدها از کلاس abstract RepositoryBase<T>
را فراخوانی کنید.
اگر میخواهید از این نوع رفتار اجتناب کنید و اجازه دهید که اکشنهای داخل کنترلر، فقط متدها را از کلاسهای repository کاربر فراخوانی کنند، تمام کاری که باید انجام دهید این است که وراثت IRepositoryBase<T>
را از IOwnerRepository حذف کنید. به این ترتیب، فقط کلاسهای repository کاربر قادر هستند که متدهای generic را از کلاس RepositoryBase<T>
فراخوانی کنند. همچنین، اکشن متدها نیز فقط با کلاسهای repository کاربر ارتباط برقرار میکنند.
این دیگر بستگی به خود شما دارد که چطور می خواهید کد و مجوزهای خود را سازماندهی کنید.
برای بررسی کردن نتیجه، میخواهیم از ابزار Postman برای برای فرستادن درخواستها به سمت اپلیکیشن استفاده کنیم.
همچنین، شما میتوانید با مطالعه مقاله چند راه عالی برای استفاده از RESTful Api در #C، در مورد نحوه استفاده از web API با برنامه نویسی از طریق #C را بیشتر یاد بگیرید.
برنامه را اجرا میکنیم. ابزار Postman را باز میکنیم و یک درخواست ایجاد میکنیم:

عالی، همه چیز طبق برنامه کار میکند.
همانطور که میبینید، ما با این اکشن، تمام داده ها را از دیتابیس برگرداندیم. البته ما میتوانیم paging را به این اکشن اضافه کنیم و با برگرداندن فقط قسمتی از داده ها، آن را بهینه سازی کنیم.
قبل از اینکه ادامه دهیم، میخواهیم مورد دیگری را به شما نشان دهیم. اگر شما به model classe ها نگاه کنید، متوجه میشوید که تمام خصوصیات، یک نام به عنوان ستون دارند که آن خصوصیات به آن مپ شده اند. اما شما میتوانید خصوصیت با نامی متفاوت از آن نامی که به آن اشاره میشود داشته باشید وآنها را به یکدیگر مپ کنید. برای این منظور، باید از ویژگی [Column] استفاده کنید.
پس اجازه دهید تا این کار را انجام دهیم.
ما میخواهیم نام خصوصیتهای AccountId
و OwnerId
در کلاسهای Owner
و Account
را به Id تغییر دهیم. همچنین میخواهیم ویژگی [Column] که ویژگی Id را به ستون مورد نظر در دیتابیس مپ میکند را اضافه کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[Table("Account")] public class Account { [Column("AccountId")] public Guid Id { get; set; } [Required(ErrorMessage = "Date created is required")] public DateTime DateCreated { get; set; } [Required(ErrorMessage = "Account type is required")] public string AccountType { get; set; } [Required(ErrorMessage = "Owner Id is required")] public Guid OwnerId { get; set; } [ForeignKey(nameof(Owner))] public Guid OwnerId { get; set; } public Owner Owner { get; set; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[Table("Owner")] public class Owner { [Column("OwnerId")] public Guid Id { get; set; } [Required(ErrorMessage = "Name is required")] [StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")] public string Name { get; set; } [Required(ErrorMessage = "Date of birth is required")] public DateTime DateOfBirth { get; set; } [Required(ErrorMessage = "Address is required")] [StringLength(100, ErrorMessage = "Address can not be loner then 100 characters")] public string Address { get; set; } public ICollection<Account> Accounts { get; set; } } |
حالا کارمان را ادامه میدهیم.
استفاده از DTO و AutoMapper
DTO یا Data Transfer Object به منظور انتقال داده ها از سرور به کلاینت به کار میرود. این دقیقا چیزی است که ما میخواهیم از آن برای انتقال داده ها از سرور به کلاینت استفاده کنیم.
اگر نگاهی به اکشن GetAllOwners بندازیم، میبینیم که ما از کلاس مدل Owner، برای واکشی داده ها از دیتابیس استفاده میکنیم (_repository.Owner.GetAllOwners()
یک لیستی از آبجکتهای Owner را برمیگرداند) و همچنین آن نتیجه را به سمت کلاینت برمیگردانیم و این روش خوبی نیست. روش بهتر این است که یک model class برای واکشی داده ها از دیتابیس و یک کلاس DTO برای برگرداندن آن نتیجه به سمت کلاینت داشته باشیم. آبجکت DTO میتواند دقیقا مشابه آبجکت مدل باشد، اما با این حال، استفاده از آبجکتهای DTO خیلی بهتر است. زیرا اگر چیزی در پایگاه داده تغییر کند ، کلاس مدل باید تغییر کند اما این بدان معنا نیست که کلاینت خواهان نتایج تغییر یافته باشد. بنابراین آبجکت DTO تغییر نخواهد کرد.
به این ترتیب، یک پوشه جدید به نام DataTransferObjects در پروژه Entities ایجاد میکنیم و کلاس OwnerDto را داخل آن ایجاد میکنیم:
1 2 3 4 5 6 7 |
public class OwnerDto { public Guid Id { get; set; } public string Name { get; set; } public DateTime DateOfBirth { get; set; } public string Address { get; set; } } |
همانطور که میبینید، ما خصوصیت Accounts را اینجا نداریم، زیرا در حال حاضر ما قصد نداریم که اطلاعات مربوط به Account ها را به کلاینت نشان دهیم.
حالا تمام کاری که باید انجام دهیم این است که یک لیست دریافت شده از owner ها از دیتابیس را به لیست ownerDto مپ کنیم. اما، انجام دادن آن به طور دستی، کاری خسته کننده است و اگر ما بیست خصوصیت و یا حتی بیشتر در کلاس DTO داشته باشیم، در این صورت زمانبر نیز خواهد بود. خوشبختانه، یک ابزار عالی برای این کار وجود دارد که به ما در مپ کردن این فرآیند، خیلی کمک خواهد کرد. بله، این ابزار، AutoMapper میباشد.
کار با AutoMapper
AutoMapper یک کتابخانه ای است که به ما در مپ کردن آبجکتهای مختلف کمک میکند. برای نصب آن، ما باید این دستور را در پنجره Package Manager Console تایپ کنیم:
1 |
PM> Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection |
بعد از نصب، باید آن را در متد ConfigureServices درون کلاس Startup ثبت کنیم:
1 |
services.AddAutoMapper(typeof(Startup)); |
حال باید یک کلاس mapping profile ایجاد کنیم تا به AutoMapper بگوییم که چطور مپ کردن اکشنها را اجرا کند. پس اجازه دهید تا یک کلاس جدید به نام MappingProfile در پروژه اصلی ایجاد کنیم آن را به این صورت تغییر دهیم:
1 2 3 4 5 6 7 |
public class MappingProfile : Profile { public MappingProfile() { CreateMap<Owner, OwnerDto>(); } } |
در آخر میتوانیم OwnerController را به این صورت تغییر دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class OwnerController : ControllerBase { private ILoggerManager _logger; private IRepositoryWrapper _repository; private IMapper _mapper; public OwnerController(ILoggerManager logger, IRepositoryWrapper repository, IMapper mapper) { _logger = logger; _repository = repository; _mapper = mapper; } [HttpGet] public IActionResult GetAllOwners() { try { var owners = _repository.Owner.GetAllOwners(); _logger.LogInfo($"Returned all owners from database."); var ownersResult = _mapper.Map<IEnumerable<OwnerDto>>(owners); return Ok(ownersResult); } catch (Exception ex) { _logger.LogError($"Something went wrong inside GetAllOwners action: {ex.Message}"); return StatusCode(500, "Internal server error"); } } } |
ما میتوانیم درخواست قبلی را از Postman بفرستیم و همان نتیجه را دریافت کنیم (بدون account ها)، اما حالا با یک پیاده سازی بهتر. AutoMapper قابلیتهای خیلی خوبی دارد و میتوانید بیشتر در مورد آن تحقیق کنید.
درخواست GetOwnerById GET در NET Core.
در ادامه، اینترفیس IOwnerRepository را تغییر میدهیم:
1 2 3 4 5 |
public interface IOwnerRepository { IEnumerable<Owner> GetAllOwners(); Owner GetOwnerById(Guid ownerId); } |
سپس اینترفیس را در OwnerRepository.cs پیاده سازی میکنیم:
1 2 3 4 5 |
public Owner GetOwnerById(Guid ownerId) { return FindByCondition(owner => owner.Id.Equals(ownerId)) .FirstOrDefault(); } |
در آخر، OwnerController را تغییر میدهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[HttpGet("{id}")] public IActionResult GetOwnerById(Guid id) { try { var owner = _repository.Owner.GetOwnerById(id); if (owner == null) { _logger.LogError($"Owner with id: {id}, hasn't been found in db."); return NotFound(); } else { _logger.LogInfo($"Returned owner with id: {id}"); var ownerResult = _mapper.Map<OwnerDto>(owner); return Ok(ownerResult); } } catch (Exception ex) { _logger.LogError($"Something went wrong inside GetOwnerById action: {ex.Message}"); return StatusCode(500, "Internal server error"); } } |
حالا میخواهیم برای بررسی نتایج، از Postman برای فرستادن درخواستهای معتبر و غیرمعتبر استفاده کنیم:

درخواست نامعتبر:

درخواست جزییات Owner
کارمان را با ایجاد logic برای برگرداندن آبجکت owner به همراه جزییات account آن ادامه میدهیم.
اول باید کلاس AccountDto را ایجاد کنیم:
1 2 3 4 5 6 |
public class AccountDto { public Guid Id { get; set; } public DateTime DateCreated { get; set; } public string AccountType { get; set; } } |
سپس باید کلاس OwnerDto مان را تغییر دهیم که به ما کمک میکند تا owner را به همراه account های مرتبط به آن را برگردانیم. اگر تمایل داشته باشید میتوانید یک کلاس DTO دیگر به نام OwnerWithAccountsDto برای این کار ایجاد کنید، اما برای سادگی کار، ما میخواهیم همان کلاس DTO موجود را تغییر دهیم:
1 2 3 4 5 6 7 8 9 |
public class OwnerDto { public Guid Id { get; set; } public string Name { get; set; } public DateTime DateOfBirth { get; set; } public string Address { get; set; } public IEnumerable<AccountDto> Accounts { get; set; } } |
دقت کنید که اینجا ویژگی Accounts، تمام account های مربوط به owner تعیین شده را bind میکند.
در نتیجه، اجازه دهید تا اینترفیسمان را تغییر دهیم:
1 2 3 4 5 6 |
public interface IOwnerRepository { IEnumerable<Owner> GetAllOwners(); Owner GetOwnerById(Guid ownerId); Owner GetOwnerWithDetails(Guid ownerId); } |
همچنین کلاس repository را تغییر میدهیم:
1 2 3 4 5 6 |
public Owner GetOwnerWithDetails(Guid ownerId) { return FindByCondition(owner => owner.Id.Equals(ownerId)) .Include(ac => ac.Accounts) .FirstOrDefault(); } |
ما اینجا از متد Include برای گرفتن تمام account های مرتبط به owner کنونی استفاده میکنیم.
حالا باید یک قانون map جدید در کلاس MappingProfile اضافه کنیم:
1 2 3 4 5 6 |
public MappingProfile() { CreateMap<Owner, OwnerDto>(); CreateMap<Account, AccountDto>(); } |
در آخر، کنترلر را تغییر میدهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[HttpGet("{id}/account")] public IActionResult GetOwnerWithDetails(Guid id) { try { var owner = _repository.Owner.GetOwnerWithDetails(id); if (owner == null) { _logger.LogError($"Owner with id: {id}, hasn't been found in db."); return NotFound(); } else { _logger.LogInfo($"Returned owner with details for id: {id}"); var ownerResult = _mapper.Map<OwnerDto>(owner); return Ok(ownerResult); } } catch (Exception ex) { _logger.LogError($"Something went wrong inside GetOwnerWithDetails action: {ex.Message}"); return StatusCode(500, "Internal server error"); } } |
نتیجه:

نتیجه گیری
درخواستهایی که از GET استفاده میکنند فقط داده ها را از دیتابیس دریافت میکنند و تمام اکشنهای داخل کلاس OwnerController با این نوع درخواست نوشته شده اند.
با خواندن این پست، شما موارد زیر را یاد گرفتید:
- نحوه کار با یک کلاس controller
- مسیریابی و نحوه استفاده از آن
- نحوه handle کردن درخواستهای GET در یک web API
- شیوه استفاده از DTO ها در زمان handle کردن درخواستها
از مطالعه این مقاله از شما تشکر میکنیم و امیدواریم که برای شما مفید واقع قرار گرفته باشد.
در مقاله بعدی، ما قصد داریم که این اصول یاد گرفته در اینجا را برای انجام درخواستهای POST, PUT و DELETE در اپلیکیشنمان اعمال کنیم.