ایجاد CRUD API در ASP.NET CORE 2.0

ASP.NET Core یک مکانیزم یک پارچه برای ساخت MVC و اپلیکیشن Web API ارائه می دهد. تفاوت کلیدی این است که Web API برای ارتباط با کلاینت JSON (یا XML) و کدهای وضعیت HTTP به جای view ها برخواهد گرداند بنابراین در این مقاله ساخت CRUD API در ASP.NET CORE 2.0 را بطور کامل بررسی خواهیم کرد.

ایجاد CRUD API در ASP.NET CORE 2.0

مساله

چگونه یک CRUD Web API با استفاده از ASP.NET Core بسازیم؟

راه حل

در یک پروژه ی خالی کلاس Startup را برای اضافه کردن سرویس ها و middleware ها به MVC به روزرسانی کنید:

 public void ConfigureServices(
            IServiceCollection services)
        {
            services.AddSingleton<IMovieService, MovieService>();
            services.AddMvc();
        }
 
        public void Configure(
            IApplicationBuilder app, 
            IHostingEnvironment env)
        {
            app.UseExceptionHandler(configure =>
            {
                configure.Run(async context =>
                {
                    var ex = context.Features
                                    .Get<IExceptionHandlerFeature>()
                                    .Error;
 
                    context.Response.StatusCode = 500;
                    await context.Response.WriteAsync($"{ex.Message}");
                });
            });
 
            app.UseMvcWithDefaultRoute();
        }

یک سرویس و مدل دامنه اضافه کنید:

public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int ReleaseYear { get; set; }
        public string Summary { get; set; }
    }    
 
    public interface IMovieService
    {
        List<Movie> GetMovies();
        Movie GetMovie(int id);
        void AddMovie(Movie item);
        void UpdateMovie(Movie item);
        void DeleteMovie(int id);
        bool MovieExists(int id);
    }

مدل های input و output (برای دریافت و ارسال داده از طریق API) را اضافه کنید:

 public class MovieInputModel
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int ReleaseYear { get; set; }
        public string Summary { get; set; }
    }
 
    public class MovieOutputModel
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int ReleaseYear { get; set; }
        public string Summary { get; set; }
        public DateTime LastReadAt { get; set; }
    }

یک controller برای API با سرویس های تزریق شده از طریق constructor اضافه کنید:

 [Route("movies")]
    public class MoviesController : Controller
    {
        [HttpGet]
        public IActionResult Get()
        {
            var model = service.GetMovies();
 
            var outputModel = ToOutputModel(model);
            return Ok(outputModel);
        }
 
        [HttpGet("{id}", Name = "GetMovie")]
        public IActionResult Get(int id)
        {
            var model = service.GetMovie(id);
            if (model == null)
                return NotFound();
 
            var outputModel = ToOutputModel(model);
            return Ok(outputModel);
        }
 
        [HttpPost]
        public IActionResult Create([FromBody]MovieInputModel inputModel)
        {
            if (inputModel == null)
                return BadRequest();
 
            var model = ToDomainModel(inputModel);
            service.AddMovie(model);
 
            var outputModel = ToOutputModel(model);
            return CreatedAtRoute("GetMovie", 
                       new { id = outputModel.Id }, outputModel);
        }
 
        [HttpPut("{id}")]
        public IActionResult Update(int id, [FromBody]MovieInputModel inputModel)
        {
            if (inputModel == null || id != inputModel.Id)
                return BadRequest();
 
            if (!service.MovieExists(id))
                return NotFound();
 
            var model = ToDomainModel(inputModel);
            service.UpdateMovie(model);
 
            return NoContent();
        }
 
        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            if (!service.MovieExists(id))
                return NotFound();
 
            service.DeleteMovie(id);
 
            return NoContent();
        }
    }

بحث

ASP.NET Core یک مکانیزم یک پارچه برای ساخت MVC و اپلیکیشن Web API ارائه می دهد. تفاوت کلیدی این است که Web API برای ارتباط با کلاینت JSON (یا XML) و کدهای وضعیت HTTP به جای view ها برخواهد گرداند.

مسیریابی

برای Web API و مسیریابی های عادی برای MVC استفاده از ویژگی هایی برمبنای مسیریابی (همراه با ویژگی های افعالی) معمول است. با URI زیر به controller دسترسی خواهید داشت:

ما همچنین می توانیم URI های تودرتو نیز تعریف کنیم برای مثال در ادامه یک Reviews controller وجود دارد که برای هر فیلم صفر یا تعداد بیشتری بازبینی ممکن است وجود داشته باشد:

توجه کنید که شناسه برای منبع والد (فیلم) بخشی از ریشه است و می تواند بخشی از اکشن متد نیز باشد. URI زیر به controller دسترسی خواهد داشت:

مدل ها

مدل در MVC اصطلاحی است که به درستی تعبیر نشده است زیرا معمولا گفته می شود که تنها یک مدل وجود دارد که درست نیست. ما انواع مختلفی از مدل ها را در اپلیکیشن خود داریم برای نمونه input ، output ، view ، domain ، entity و غیره. این مدل ها بخش هایی از لایه های متفاوت هستند و انتزاعی از مفاهیم مختلف هستند. کتاب های DDD و SOLID را برای مباحث دقیق تر بررسی کنید.

نکته ی کلیدی زمان توسعه ی Web API این است که مدل هایی که توسط controller ارسال و دریافت می شوند Data Transfer Objects (DTO) هستند و جدای از مدل های domain یا entity است.

بازیابی (GET)

یک GET موفق کد وضعیت 200 (OK) با داده می فرستد.

اگر داده ای پیدا نشود کد 404 (Not Found) بازگردانده می شود.

ساخت (POST)

POST موفق کد 201 (Created) برمی گرداند و header مربوط به location را روی HTTP response تنظیم می کند تا به URI آیتم جدید اشاره کند.

برای موضوعاتی با مدل input یک کد 400 (Bad Request) برگردانده می شود. همچنین برای پاسخ های تودرتو اگر والد برگردانده نشود یک کد 404 (Not Found) برگردانده می شود.

Update (PUT)

یک PUT موفق کد 204 (No Content) برمی گرداند.

برای مسائل با مدل input کد 400 (Bad Request) برگردانده می شود. همچنین برای منابع تودرتو اگر والد پیدا نشود کد 404 (Not Found) برگردانده می شود.

Update (PATCH)

اگرچه این مورد کمتر استفاده می شود می توانیم اجزای داده را با استفاده از افعال PATCH به روزرسانی کنیم.

Content-Type برای به روزرسانی PATCH باید application/json-patch+json باشد. بدنه ی Request نیاز دارد که شامل یک آرایه ای از عملیات patch باشد تا به مدل خودمان اعمال کنیم و پارامتر های اکشن نیاز به استفاده از JsonPatchDocument<T> برای دریافت این عملیات ها دارند:

   [HttpPatch("{id}")]
        public IActionResult UpdatePatch(
            int id, [FromBody]JsonPatchDocument<MovieInputModel> patch)
        {
            if (patch == null)
                return BadRequest();
 
            var model = service.GetMovie(id);
            if (model == null)
                return NotFound();
 
            var inputModel = ToInputModel(model);
            patch.ApplyTo(inputModel);
 
            TryValidateModel(inputModel);
            if (!ModelState.IsValid)
                return new UnprocessableObjectResult(ModelState);
 
            model = ToDomainModel(inputModel);
            service.UpdateMovie(model);
 
            return NoContent();
        }

در اینجا ما 1) یک مدل را از منبع داده بازیابی می کنیم. 2) آن را به مدل input تبدیل می کنیم 3) از patch ، apply  می کنیم 4) اعتبار سنجی می کنیم و5) منبع داده را به روزرسانی می کنیم.

جدول زیر عملیات های patch را و توضیح استفاده از آن ها را لیست می کند:

Delete (DELETE)

یک DELETE موفق کد 204 (No Content) را برمی گرداند.

برای منابع تودرتو اگر والد پیدا نشود کد 404 (Not Found) برگردانده می شود.

اعتبارسنجی (POST/PUT/PATCH)

مدل های Input همانطور که قبلا دراینجا بحث کردیم می توانند با تفسیر داده ها یا کد های معمول اعتبارسنجی شوند. کد وضعیت 422 (Unprocessable Entity) برای نشان دادن عدم موفقیت در اعتبارسنجی، به کاربر برگردانده می شود. در اینجا هیچ Action Result آماده ای برای این کار نداریم اما ساخت آن ها آسان است:

public class UnprocessableObjectResult : ObjectResult
    {
        public UnprocessableObjectResult(object value) 
            : base(value)
        {
            StatusCode = StatusCodes.Status422UnprocessableEntity;
        }
 
        public UnprocessableObjectResult(ModelStateDictionary modelState) 
            : this(new SerializableError(modelState))
        { }
    }

حال در POST/PUT/PATCH می توانید مسائل مورد اعتبارسنجی را بررسی کنید و این نتیجه را بازگردانید:

  if (!ModelState.IsValid)
                return new UnprocessableObjectResult(ModelState);

دراین کد نمونه یک متد helper در base controller ساخته ایم:

         if (!ModelState.IsValid)
                return Unprocessable(ModelState);
 
    public class BaseController : Controller
    {
        [NonAction]
        public UnprocessableObjectResult Unprocessable(
           ModelStateDictionary modelState)
        {
            return new UnprocessableObjectResult(modelState);
        }
 
        [NonAction]
        public ObjectResult Unprocessable(object value)
        {
            return new UnprocessableObjectResult(value);
        }
    }

توجه: فایل Postman شامل نمونه درخواست های HTTP برای GET, POST, PUT, DELETE و PATCH می باشد.

سورس کد : (GitHub)