ایجاد یک WebAPI در MVC6

یکشنبه 8 شهریور 1394

در این مقاله نشان خواهیم داد که چگونه با استفاده از ASP.NET MVC6 یک web API ساده ایجاد کنیم.

ایجاد یک WebAPI در MVC6

یکی از اهداف ASP.Net 5 یکی کردن MVC و فریمورک Web API می باشد.

در این مقاله خواهید آموخت که:

چگونه یک WebAPI ساده در ASP.NET MVC 6 ایجاد کنیم.

چگونه از یک قالب خالی شروع کنید و کامپوننت هایی را که نیاز دارید به برنامه خود اضافه کنید.

چگونه ASP.NET pipeline را تنظیم کنید.

چگونه برنامه خود را در خارج از IIS میزبانی کنیم.

ایجاد یک پروژه Empty ASP.NET

Visual Studio 2015 را باز کرده و از منوی File مسیر زیر را طی کنید.

New-->Project-->Template-->Visual C#-->Web-->ASP.NET Web Application

نام پروژه را انتخاب کنید و در آخر روی ok کلیک کنید.

در پنجره ای که باز می شود گزینه New ASP.NET را انتخاب کنید.

 

در تصویر زیر ساختار پروژه را می بینید.

پروژه شامل فایل های زیر می باشد.

global.json شامل تنظیمات solution-level بوده و ارجاع های پروژه به پروژه را  فراهم می سازد.

project.json شامل تنظیمات پروژه می باشد.

project_Readme.html یک فایل خواندنی می باشد.

Sturtup.cs شامل کدهای راه اندازی و پیکربندی پروژه می باشد.

کلاس Sturtup در sturtup.cs تعریف شده، درخواست pipeline در ASP.NET را پیکربندی می کند. وقتی ما از قالب Empty برای پروژه مان استفاده می کنیم، کلاس sturtup بدون اضافه کردن هیچ چیزی به pipeline شروع می شود.

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // Nothing here!
    }
}

 اضافه کردن صفحه خوش آمدگویی(Welcome)

فایل project.json را باز کنید. این فایل شامل تنظیمات برنامه می باشد. لیست بخش وابستگی های موردنیاز بسته NuGet و  کتابخانه کلاس ها می باشد. بسته Microsoft.AspNet.Diagnostics را به لیست اضافه می کنیم.

"dependencies": {
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta1",
    // Add this: 
    "Microsoft.AspNet.Diagnostics": "1.0.0-beta1"
},

ویژوال استدیو یک intellisence برای ما نمایان می کند که یک لیست از بسته های موجود می باشد.

همچنین یک intellisence برای ورژن بسته ها خواهید داشت.

فایل Sturtup.cs را باز کنید و کدهای زیر را به آن اضافه کنید.

using System;
using Microsoft.AspNet.Builder;


namespace TodoApi
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            // New code
            app.UseWelcomePage();
        }
    }
}

کلید F5 را فشار دهید تا دیباگ برنامه شروع شود، Visual Studio مرورگر را باز می کند و به آدرس http://localhost:port/ هدایت می شوید، که شماره پورت به صورت random توسط ویژوال استدیو تخصیص می یابد. اکنون باید مانند تصویر زیر صفحه Welcom را ببینید.

صفحه Welcom یک را ه سریع برای چیزی در حال اجرا، بدون نوشتن کد برنامه می باشد.

ایجاد یک Web API

در این بخش، یک web API برای مدیریت یک لیست از بخش های ToDo ایجاد خواهیم کرد. ایتدا نیاز داریم که ASP.NET MVC 6 را به برنامه اضافه کنیم.

بسته MVC 6 را به لیست موردنیاز در project.json اضافه کنید:

 

"dependencies": {
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta1",
    "Microsoft.AspNet.Diagnostics": "1.0.0-beta1",
    // New:
    "Microsoft.AspNet.Mvc": "6.0.0-beta1"
},

سپس MVC را به درخواست های pipeline در Sturtup.cs اضافه کنید.

   1- عبارت using  Microsoft.Framework.DependencyInjection را اضافه کنید.

   2- متد زیر را به کلاس Sturtup اضافه کنید.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
}

این کد، تمام آنچه MVC 6 نیاز دارد را اضافه می کند.فریمورک به صورت خودکار ConfigureServices را در راه اندازی فراخوانی می کند.

   3- در متد configure کدهای زیر را اضافه کنید. MVC 6 توسط متد UseMvc به pipeline اضافه می شود.

public void Configure(IApplicationBuilder app)
{
    // New:
    app.UseMvc();
}

کلاس کامل Sturtup را بعد از تغییراتش در اینجا مشاهده می نمایید.

using System;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
// New using:
using Microsoft.Framework.DependencyInjection;

namespace TodoApi
{
    public class Startup
    {
        // Add this method:
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            // New:
            app.UseMvc();
            app.UseWelcomePage();
        }
    }
}

اضافه کردن یک Model

یک Model یک کلاس است که اطلاعات دامنه در یک برنامه را نشان می دهد. در این مورد، متد یکی از آیتم های کلاس ToDo است. کلاس زیر را به برنامه اضافه کنید.

using System.ComponentModel.DataAnnotations;

namespace TodoApi.Models
{
    public class TodoItem
    {
        public int Id { get; set; }
        [Required]
        public string Title { get; set; }
        public bool IsDone { get; set; }
    }
}

برای حفظ سازمان یافتگی پروژه، یک پوشه Models ایجاد کرده و کلاس model را در آن قرار دهید.

اضافه کردن یک  Controller

 یک Controller، یک کلاس است که درخواست های HTTP را در آن دسته بندی می کند. کلاس زیر را به پروژه اضافه کنید.

using Microsoft.AspNet.Mvc;
using System.Collections.Generic;
using System.Linq;
using TodoApi.Models;

namespace TodoApi.Controllers
{

    [Route("api/[controller]")]
    public class TodoController : Controller
    {
        static readonly List<TodoItem> _items = new List<TodoItem>()
        {
            new TodoItem { Id = 1, Title = "First Item" }
        };

        [HttpGet]
        public IEnumerable<TodoItem> GetAll()
        {
            return _items;
        }

        [HttpGet("{id:int}", Name = "GetByIdRoute")]
        public IActionResult GetById (int id)
        {
            var item = _items.FirstOrDefault(x => x.Id == id);
            if (item == null)
            {
                return HttpNotFound();
            }

            return new ObjectResult(item);
        }

        [HttpPost]
        public void CreateTodoItem([FromBody] TodoItem item)
        {
            if (!ModelState.IsValid)
            {
                Context.Response.StatusCode = 400;
            }
            else
            {
                item.Id = 1+ _items.Max(x => (int?)x.Id) ?? 0;
                _items.Add(item);

                string url = Url.RouteUrl("GetByIdRoute", new { id = item.Id }, 
                    Request.Scheme, Request.Host.ToUriComponent());

                Context.Response.StatusCode = 201;
                Context.Response.Headers["Location"] = url;
            }
        }

        [HttpDelete("{id}")]
        public IActionResult DeleteItem(int id)
        {
            var item = _items.FirstOrDefault(x => x.Id == id);
            if (item == null)
            {
                return HttpNotFound();
            }
            _items.Remove(item);
            return new HttpStatusCodeResult(204); // 201 No Content
        }
    }
}

یک پوشه Controllers برای Controller ایجاد کرده ایم. این controller برخی از عملیات CRUD را پیاده سازی می کند.

درخواست(متد HTTP و URL ) و شرح آن ها

GET/api/todo : تمام بخش های ToDo را برمی گرداند.

GET/api/todo/id : بخش ToDo را با ID که در URL بدست می آورد برمی گرداند.

POST/api/todo : یک بخش جدید ToDo ایجاد می کند. کلاینت بخش ToDo را به بدنه درخواست ارسال می کند.

DELETE/api/todo/id : بخش ToDo را پاک می کند.

برای مثال، در اینجا یک درخواست HTTP وجود دارد که لیست بخش های ToDo را بدست می آورد:

GET http://localhost:5000/api/todo HTTP/1.1
User-Agent: Fiddler
Host: localhost:5000

پاسخ این است:

HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Thu, 30 Oct 2014 22:40:31 GMT
Content-Length: 46

[{"Id":1,"Title":"First Item","IsDone":false}]

 

بررسی کد

در این بخش، توضیح مختصری درمورد بعضی از کدهای کلاس TodoController خواهیم داشت.

مسیریابی

خصوصیت Route قالب URL برای controller را تعیین می کند.

[Route("api/[controller]")]

هر درخواست HTTP که با قالب (template) مطابقت کند به controller هدایت می شود. در این مثال، "[controller]" به معنی تعویض نام کلاس controller، منهای پسوند Controller می باشد. برای کلاس TodoController، یعنی، قالب (template) مسیر "api/todo" می باشد.

متدهای HTTP

خصوصیت های [HttpGet]، [HttpPost] و [HttpDelete] متد HTTP رابرای عملیات controller تعریف می کند. 

[HttpGet]
public IEnumerable<TodoItem> GetAll() {}

[HttpGet("{id:int}", Name = "GetByIdRoute")]
public IActionResult GetById (int id) {}

[HttpPost]
public void CreateTodoItem([FromBody] TodoItem item) {}

[HttpDelete("{id:int}")]
public IActionResult DeleteItem(int id) {}

GetById و DeleteItem، آرگومان رشته ای، بخش بیشتری را به rout (مسیر) اضافه می کنند. بنابراین برای این متدها، قالب کامل route (مسیر) "{api/[controller]/{id:int" می باشد.

در بخش "{id:int}" کلمه id یک متغیر است و int: نوع متغیر می باشد. بنابراین، این آدرس ها (URLS) مطابقت می کند:

http://localhost/api/todo/1
http://localhost/api/todo/42

اما با آدرس زیر مطابقت نمی کند:

http://localhost/api/todo/abc

توجه کنید که GetById و DeleteItem پارامتری به نام id دارد. فریمورک، پارامتر id را از بخش متناظر پر خواهد کرد. برای مثال اگر آدر درخواست شده، http://localhost/api/todo/42 باشد، مقدار پارامتر id برابر 42 می شود. این فرآیند، parameter binding نامیده می شود.

CreatTodoItem شکل دیگری از parameter binding را نشان می دهد.

[HttpPost]
public void CreateTodoItem([FromBody] TodoItem item) {}

صفت [FromBody] می گوید که فریمورک پارامتر TodoItem  از بدنه درخواست را deserialize کند.

جدول زیر لیست برخی مثال های درخواست ها و controller action که مطابقت دارد را نشان می دهد. 

دو مثال آخر، error 404 را به دلایل مختلف بر می گرداند. در این مورد، 'abc' با نوع integer متد GetById مطابقت نمی کند. در مورد 'PUT/api/todo' هیچ controll action با صفت [HttpPut] وجود ندارد.

عمل برگرداندن مقادیر

کلاس TodoController چندین روش را برای برگرداندن یک مقدار از یک controller action نشان می دهد.

متد GetAll یک شی CLR را برمی گرداند.

[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
    return _items;
}

شی برگردانده شده در بدنه پیام پاسخ serialize شده است. فرمت پیش فرض json می باشد، اما کلاینت می تواند فرمت دیگری را درخواست کند. برای مثال در اینجا یک درخواست HTTP وجود دارد که برای یک پاسخ XML سوال می پرسد.

GET http://localhost:5000/api/todo HTTP/1.1
User-Agent: Fiddler
Host: localhost:5000
Accept: application/xml

پاسخ:

HTTP/1.1 200 OK
Content-Type: application/xml;charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Thu, 30 Oct 2014 22:40:10 GMT
Content-Length: 228

<ArrayOfTodoItem xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/TodoApi.Models"><TodoItem><Id>1</Id><IsDone>false</IsDone><Title>First Item</Title></TodoItem></ArrayOfTodoItem>

متد GetById یک IActionResult را برمی گرداند.

[HttpGet("{id:int}", Name = "GetByIdRoute")]
public IActionResult GetById (int id)
{
    var item = _items.FirstOrDefault(x => x.Id == id);
    if (item == null)
    {
        return HttpNotFound();
    }

    return new ObjectResult(item);
}

این متد، اگر یک بخش ToDo را با یک ID منطبق پیدا کند، objectResult را برمی گرداند. objectResult برگردانده شده برابر با مدل CLR می باشد، اما نوع برگشتی را IActionResult ایجاد می کند. به این ترتیب، متد می تواند یک action result متفاوت را برای مسیر کد دیگر برگرداند.

زمانیکه ID پیدا نشد، متد HttpNotFound را برمی گرداند، که به پاسخ 404 ترجمه می شود.

در نهایت متد CreatTodoItem نشان می دهد که چگونه پاسخ را براساس مقادیر تنظیم شده بطور مستقیم در ویژگی Response در controller ایجاد می کند. در این مورد، نوع برگشتی void می باشد.

[HttpPost]
public void CreateTodoItem([FromBody] TodoItem item)
{
    // (some code not shown here)

    Context.Response.StatusCode = 201;
    Context.Response.Headers["Location"] = url;
}

یک اشکال در این روش این است که، برای تست واحد سخت تر می باشد.

وابستگی به تزریق (Dependency Injection)

MVC 6 دارای وابستگی به تزریق در فریمورک می باشد. برای دیدن این مطلب، یک کلاس انبار برای نگه داشتن لیست بخش های ToDo ایجاد میکنیم.

ابتدا، یک Interface برای انبار تعریف می کنیم:

using System.Collections.Generic;

namespace TodoApi.Models
{
    public interface ITodoRepository
    {
        IEnumerable<TodoItem> AllItems { get; }
        void Add(TodoItem item);
        TodoItem GetById(int id);
        bool TryDelete(int id);
    }
}

سپس یک  concrete implementation تعریف کنید.

using System;
using System.Collections.Generic;
using System.Linq;

namespace TodoApi.Models
{
    public class TodoRepository : ITodoRepository
    {
        readonly List<TodoItem> _items = new List<TodoItem>();

        public IEnumerable<TodoItem> AllItems
        {
            get
            {
                return _items;
            }
        }

        public TodoItem GetById(int id)
        {
            return _items.FirstOrDefault(x => x.Id == id);
        }

        public void Add(TodoItem item)
        {
            item.Id = 1 + _items.Max(x => (int?)x.Id) ?? 0;
            _items.Add(item);
        }

        public bool TryDelete(int id)
        {
            var item = GetById(id);
            if (item == null)
            {
                return false;
            }
            _items.Remove(item);
            return true;
        }
    }
} 

از تزریق سازنده (constructor injection) برای تزریق انبار ایجاد شده در controller استفاده می کنیم.

[Route("api/[controller]")]
public class TodoController : Controller
{
    // Remove this code:
    //static readonly List<TodoItem> _items = new List<TodoItem>()
    //{
    //    new TodoItem { Id = 1, Title = "First Item" }
    //};

    // Add this code:
    private readonly ITodoRepository _repository;

    public TodoController(ITodoRepository repository)
    {
        _repository = repository;
    }

سپس متد controller  را برای استفاده از انبار بروز رسانی کنید. 

[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
    return _repository.AllItems;
}
[HttpGet("{id:int}", Name = "GetByIdRoute")]
public IActionResult GetById(int id)
{
    var item = _repository.GetById(id);
    if (item == null)
    {
        return HttpNotFound();
    }

    return new ObjectResult(item);
}

[HttpPost]
public void CreateTodoItem([FromBody] TodoItem item)
{
    if (!ModelState.IsValid)
    {
        Context.Response.StatusCode = 400;
    }
    else
    {
        _repository.Add(item);

        string url = Url.RouteUrl("GetByIdRoute", new { id = item.Id }, Request.Scheme, Request.Host.ToUriComponent());
        Context.Response.StatusCode = 201;
        Context.Response.Headers["Location"] = url;
    }
}

[HttpDelete("{id}")]
public IActionResult DeleteItem(int id)
{
    if (_repository.TryDelete(id))
    {
        return new HttpStatusCodeResult(204); // 201 No Content
    }
    else
    {
        return HttpNotFound();
    }
}

برای کار تزریق وابستگی، ما نیاز داریم که انبار را با سیستم تزریق وابستگی ثبت کنیم. در کلاس Startup کدهای زیر را اضافه کنید:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    // New code
    services.AddSingleton<ITodoRepository, TodoRepository>();
}

زمانی که برنامه اجرا می شود، هروقت که یک نمونه controller ایجاد کرد، فریمورک بطور خودکار، TodoRepository را به controller تزریق می کند. زیرا ما AddSingleton را برای ثبت ITodoRepository استفاده کرده ایم، همان نمونه در تمام طول عمر نرم افزار استفاده می شود.

اجرای برنامه خارج از IIS

بطور پیش فرض وقتی کلید F5 را فشار می دهید، برنامه در IIS Express اجرا می شود. می توانید این را با دیدن آیکن IIS Express که در taskbar ظاهر می شود، ببینید.

 

ASP.NET 5 با یک pluggable server layer طراحی شده است. این بدان معناست که می توانیم در یک وب سرور متفاوت جابجا شوید. در این بخش، ما بجای اجرا شدن درون IIs، به WebListener جابجا می شویم، که بطور مستقیم در درایور هسته Http.Sys اجرا می شود.

اجرا در IIS مزایای زیادی مانند امنیت، مدیریت فرآیند، مدیریت GUI و غیره را دارد. نکته این است که ASP.NET 5 بطور مستقیم با IIS گره نخورده است. بنابراین برنامه را درون یک برنامه کنسول میزبانی کردیم، اجرا کنیم.

در فایل project.json بسته Microsoft.AspNet.Server.WebListener را اضافه کنید:

"dependencies": {
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta1",
    "Microsoft.AspNet.Diagnostics": "1.0.0-beta1",
    "Microsoft.AspNet.Mvc": "6.0.0-beta1",
    // New:
    "Microsoft.AspNet.Server.WebListener": "6.0.0-beta1"
},

 

سپس گزینه های سطح بالای زیر را در project.json اضافه کنید.

{
    // Other sections not shown

    "commands": {
        "web ": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000"
    }
}

گزینه "commands" شامل یک لیست از دستورات از پیش تعریف شده می باشد. در این مثال، "web" نام یک دستور است. که می تواند یک رشته دلخواه باشد. value یک دستور واقعی است.

     - Microsoft.AspNet.Hosting  یک assembly است که برای میزبانی برنامه ASP.NET 5 استفاده می شود.

   - server-- در WebListener سرور را  تعیین می کند.

   - server.urls--  آدرس (URL) را برای گوش دادن به آن بدست می آورد.

فایل project.json را ذخیره کنید. سپس، در Solution Explorer روی پروژه راست کلیک کرده و گزینه Prppertice را انتخاب کنید. در برگه Propertice روی گزینه Debug کلیک کنید. از منوی کشویی مربوط به گزینه Debug target بجای گزینه IIS Express گزینه wb را انتخاب کنید.

کلید F5 را فشار دهید تا برنامه اجرا شود. بجای اجرای برنامه در مرورگر ویندوز، Visual Studio یک برنامه کنسول را اجرا کرده که WebListener را شروع می کند.

یک مرورگر را باز کنید و به آدرس http://localhost:5000 بروید. (مقدار ی که در فایل project.json مشخص کردید.) باید صفحه Welcome را ببینید.

برای اجرا در IIS Explorer کافیست در Debug Target گزینه IIS Express را انتخاب کنید.

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید