# ایجاد HTTP API توسط Feather HTTP

Feather HTTP یک فریم‌ورک HTTP سبک، برای ایجاد APIهای NET Core. است، در واقع یک wrapper بر روی APIهای موجود ASP.NET Core می‌باشد که به ما امکان ایجاد HTTP API را در کمترین زمان میدهد. در این مطلب نحوه 

- Published: 2020-07-26
- Language: fa
- Tags: DNTips
- Canonical: https://sirwan.info/blog/fa/dntips-3219

---

> این نوشته نخستین بار در [دات‌نت تیپس](https://www.dntips.ir/post/3219) منتشر شده است.

<div class="postBody"><a href="https://github.com/featherhttp/framework">Feather HTTP</a> یک فریم‌ورک HTTP سبک، برای ایجاد APIهای NET Core. است، در واقع یک wrapper بر روی APIهای موجود ASP.NET Core می‌باشد که به ما امکان ایجاد HTTP API را در کمترین زمان میدهد. در این مطلب نحوه ایجاد یک API را توسط این فریم‌ورک بررسی خواهیم کرد.<div> <br/> </div> <div> <b>معرفی قالب FeatherHttp.Templates به سیستم dotnet </b> </div> <div>برای شروع می‌توانیم قالب پروژه Feather HTTP را به لیست قالب‌های از پیش نصب شده‌ی dotnet اضافه کنیم. برای اینکار کافی است در خط فرمان دستور زیر را وارد کنیم:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">dotnet new -i FeatherHttp.Templates::0.1.67-alpha.g69b43bed72 --nuget-source https://f.feedz.io/featherhttp/framework/nuget/index.json</pre>
 </div>
پس از نصب قالب می‌توانید Feather HTTP را در لیست قالب‌ها توسط دستور dotnet new --list مشاهده کنید:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">Templates                                         Short Name               Language          Tags&#10;----------------------------------------------------------------------------------------------------------------------------------&#10;FeatherHttp                                       feather                  [C#]              Web/ASP.NET/FeatherHttp</pre>
 </div> <br/> </div> <div> <b style="box-sizing: border-box; color: rgb(51, 51, 51);">نحوه‌ی ایجاد یک پروژه‌ی جدید بر اساس قالب جدید </b> <br/> </div> <div> <span style="color: #333333;">برای ایجاد یک پروژه‌ی جدید کافی است از دستور dotnet new feather استفاده کنید، در ادامه یک پروژه جدید تحت عنوان todoAPI ایجاد خواهیم کرد:</span> </div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">dotnet new feather --name todoAPI</pre>
 </div>
خروجی دستور فوق یک پروژه با ساختار ذیل است:</div> <div> <p style="margin-left: auto; margin-right: auto;"> <img src="/img/dntips/b8ffea629b62d3ce5546.png" style="display: block; margin-left: auto; margin-right: auto; cursor: default;"/> </p> <p style="margin-left: auto; margin-right: auto;">همانطور که مشاهده می‌کنید پروژه‌ی فوق تنها شامل دو فایل .csproj و Program.cs است. درون Program.cs و متد Main کار initialize کردن سرور HTTP صورت گرفته است. WebApplication.Create دقیقا همانند Host.CreateDefaultBuilder پروژه‌های ASP.NET Core عمل می‌کند؛ یعنی پیکربندی pipeline از قبیل اضافه کردن متغیرهای محیطی، خواندن از فایل JSON و ... را انجام میدهد اما با کد boilerplate کمتر. بنابراین خروجی WebApplication.Create یک ASP.NET Core Pipeline با قابلیت اضافه کردن تنظیمات دلخواه است. در ادامه جهت بررسی بیشتر Feather HTTP، یک مدل را به همراه یک سری دیتای In-memory به پروژه اضافه خواهیم کرد:</p> <div align="left" dir="ltr" style="direction: ltr;"> </div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using System.Collections.Generic;&#10;using System.Text.Json.Serialization;&#10;using System.Linq;&#10;&#10;namespace todoAPI.Models&#10;{&#10;    public class Todo&#10;    {&#10;        [JsonPropertyName("id")]&#10;        public int Id { get; set; }&#10;        [JsonPropertyName("title")]&#10;        public string Title { get; set; }&#10;        [JsonPropertyName("completed")]&#10;        public bool Completed { get; set; }&#10;    }&#10;&#10;    public class TodoData&#10;    {&#10;        private readonly IList&lt;Todo&gt; _db = new List&lt;Todo&gt;&#10;        {&#10;            new Todo { Id = 1, Title = "Read book" },&#10;            new Todo { Id = 2, Title = "Watch an episode of Dark" },&#10;            new Todo { Id = 3, Title = "Publish a post on dotnettips" },&#10;            new Todo { Id = 4, Title = "Skype with my friend" },&#10;        };&#10;        public IList&lt;Todo&gt; GetAllToDoItmes()&#10;        {&#10;            return _db;&#10;        }&#10;        public void AddTodo(Todo item)&#10;        {&#10;            _db.Add(item);&#10;        }&#10;        public void ToggleTodo(int id)&#10;        {&#10;            var todo = _db.FirstOrDefault(x =&gt; x.Id == id);&#10;            todo.Completed = !todo.Completed;&#10;        }&#10;&#10;        public void DeleteTodo(int id)&#10;        {&#10;            var todo = _db.FirstOrDefault(x =&gt; x.Id == id);&#10;            _db.Remove(todo);&#10;        }&#10;    }&#10;}</pre>
 </div> <br/> <p style="margin-left: auto; margin-right: auto;">در مثال فوق برای نگاشت نام خواص، از System.Text.Json توکار <a href="https://www.dntips.ir/post/3063">NET Core 3.0</a>. استفاده شده‌است. در ادامه نیز از یک کلاس برای شبیه‌سازی CRUD یک Todo استفاده شده‌است. سپس برای داشتن اندپوینت‌های موردنظر به ازای هر کدام از متدهای فوق درون متد Main، از app.Map... استفاده کرده‌ایم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using System.Threading.Tasks;&#10;using Microsoft.AspNetCore.Builder;&#10;using Microsoft.AspNetCore.Http;&#10;using todoAPI.Models;&#10;&#10;namespace todoAPI&#10;{&#10;    class Program&#10;    {&#10;        private static readonly TodoData db = new TodoData();&#10;        static async Task Main(string[] args)&#10;        {&#10;            var app = WebApplication.Create(args);&#10;&#10;            app.MapGet("/", GetTodos);&#10;            app.MapPost("/api/todos", CreateTodo);&#10;            app.MapPost("/api/todos/{id}", ToggleTodo);&#10;            app.MapDelete("/api/todos/{id}", DeleteTodo);&#10;&#10;            await app.RunAsync();&#10;        }&#10;&#10;        static async Task GetTodos(HttpContext http)&#10;        {&#10;            var todos = db.GetAllToDoItmes();&#10;            await http.Response.WriteJsonAsync(todos);&#10;        }&#10;&#10;        static async Task CreateTodo(HttpContext http)&#10;        {&#10;            var todo = await http.Request.ReadJsonAsync&lt;Todo&gt;();&#10;            db.AddTodo(todo);&#10;            http.Response.StatusCode = 204;&#10;        }&#10;&#10;        static async Task ToggleTodo(HttpContext http)&#10;        {&#10;            if (!http.Request.RouteValues.TryGet("id", out int id))&#10;            {&#10;                http.Response.StatusCode = 400;&#10;                return;&#10;            }&#10;            db.ToggleTodo(id);&#10;            http.Response.StatusCode = 204;&#10;        }&#10;&#10;        static async Task DeleteTodo(HttpContext http)&#10;        {&#10;            if (!http.Request.RouteValues.TryGet("id", out int id))&#10;            {&#10;                http.Response.StatusCode = 400;&#10;                return;&#10;            }&#10;            db.DeleteTodo(id);&#10;            http.Response.StatusCode = 204;&#10;        }&#10;    }&#10;}</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">هر کدام از اندپوینت‌های فوق، یک ورودی HttpContext دریافت خواهند کرد. توسط این شیء می‌توانیم به درخواست جاری و همچنین به پاسخ درخواست، دسترسی داشته باشیم. </p> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <b>استفاده از سیستم DI توکار NET Core.</b> </p> <p style="margin-left: auto; margin-right: auto;">همانطور که در ابتدای مطلب نیز عنوان شد، Feather HTTP یک wrapper بر روی APIهای موجود ASP.NET Core است، بنابراین می‌توانیم از همان سرویس DI که درون پروژه‌های ASP.NET Core در اختیار داریم در اینجا نیز استفاده کنیم. در ادامه یک پوشه‌ی جدید را به مثال قبل، با نام Controllers اضافه خواهیم کرد و درون آن یک فایل TodoController را با محتویات زیر ایجاد خواهیم کرد:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using System.Threading.Tasks;&#10;using Microsoft.AspNetCore.Http;&#10;using todoAPI.Models;&#10;using todoAPI.Services;&#10;&#10;namespace todoAPI.Controllers&#10;{&#10;    public class TodoController&#10;    {&#10;        private readonly ITodoService _todoService;&#10;&#10;        public TodoController(ITodoService todoService)&#10;        {&#10;            _todoService = todoService;&#10;        }&#10;&#10;        public async Task GetTodos(HttpContext http)&#10;        {&#10;            var todos = _todoService.GetAllToDoItmes();&#10;            await http.Response.WriteJsonAsync(todos);&#10;        }&#10;&#10;        public async Task CreateTodo(HttpContext http)&#10;        {&#10;            var todo = await http.Request.ReadJsonAsync&lt;Todo&gt;();&#10;            _todoService.AddTodo(todo);&#10;            http.Response.StatusCode = 204;&#10;        }&#10;&#10;        public async Task ToggleTodo(HttpContext http)&#10;        {&#10;            if (!http.Request.RouteValues.TryGet("id", out int id))&#10;            {&#10;                http.Response.StatusCode = 400;&#10;                return;&#10;            }&#10;            _todoService.ToggleTodo(id);&#10;            http.Response.StatusCode = 204;&#10;        }&#10;&#10;        public async Task DeleteTodo(HttpContext http)&#10;        {&#10;            if (!http.Request.RouteValues.TryGet("id", out int id))&#10;            {&#10;                http.Response.StatusCode = 400;&#10;                return;&#10;            }&#10;            _todoService.DeleteTodo(id);&#10;            http.Response.StatusCode = 204;&#10;        }&#10;    }&#10;}</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">کاری که انجام شده است، انتقال تمامی متدهای static به کلاس فوق و سپس جایگزین کردن کلمه‌ی کلیدی static با public است. همچنین یه ارجاع به اینترفیس جدید با عنوان ITodoService اضافه شده است؛ درون پیاده‌سازی این اینترفیس همان متدهای کلاس TodoData را اضافه کرده‌ایم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using System.Collections.Generic;&#10;using todoAPI.Models;&#10;using System.Linq;&#10;&#10;namespace todoAPI.Services&#10;{&#10;    public interface ITodoService&#10;    {&#10;        void AddTodo(Todo item);&#10;        void DeleteTodo(int id);&#10;        IList&lt;Todo&gt; GetAllToDoItmes();&#10;        void ToggleTodo(int id);&#10;    }&#10;&#10;    public class TodoService : ITodoService&#10;    {&#10;        private readonly IList&lt;Todo&gt; _db = new List&lt;Todo&gt;&#10;        {&#10;            new Todo { Id = 1, Title = "Read book" },&#10;            new Todo { Id = 2, Title = "Watch an episode of Dark" },&#10;            new Todo { Id = 3, Title = "Publish a post on dotnettips" },&#10;            new Todo { Id = 4, Title = "Skype with my friend" },&#10;        };&#10;        public IList&lt;Todo&gt; GetAllToDoItmes()&#10;        {&#10;            return _db;&#10;        }&#10;        public void AddTodo(Todo item)&#10;        {&#10;            _db.Add(item);&#10;        }&#10;        public void ToggleTodo(int id)&#10;        {&#10;            var todo = _db.FirstOrDefault(x =&gt; x.Id == id);&#10;            todo.Completed = !todo.Completed;&#10;        }&#10;&#10;        public void DeleteTodo(int id)&#10;        {&#10;            var todo = _db.FirstOrDefault(x =&gt; x.Id == id);&#10;            _db.Remove(todo);&#10;        }&#10;    }&#10;}</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <b>نکته</b>: برای ایجاد اینترفیس از روی یک کلاس درون VS Code می‌توانیم اینگونه عمل کنیم:</p> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <img src="/img/dntips/44c3e4f78ea652c6006a.png" style="display: block; margin-left: auto; margin-right: auto; cursor: default;"/> </p> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <b>تغییرات فایل Program.cs</b> </p> <p style="margin-left: auto; margin-right: auto;">ابتدا باید using مربوط به DI را در ابتدای فایل اضافه کنیم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using Microsoft.Extensions.DependencyInjection;</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">سپس توسط ServiceProvider یک وهله از کلاس موردنظر را ایجاد کرده‌ایم و همچنین سرویس‌های موردنظر را درون DI Container اضافه کرده‌ایم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using System.Threading.Tasks;&#10;using Microsoft.AspNetCore.Builder;&#10;using Microsoft.Extensions.DependencyInjection;&#10;using todoAPI.Controllers;&#10;using todoAPI.Services;&#10;&#10;namespace todoAPI&#10;{&#10;    class Program&#10;    {&#10;        static async Task Main(string[] args)&#10;        {&#10;            var builder = WebApplication.CreateBuilder(args);&#10;            builder.Services.AddTransient&lt;TodoController&gt;();&#10;            builder.Services.AddTransient&lt;ITodoService, TodoService&gt;();&#10;&#10;            var serviceProvider = builder.Services.BuildServiceProvider();&#10;            var todoController = serviceProvider.GetService&lt;TodoController&gt;();&#10;&#10;            var app = WebApplication.Create(args);&#10;&#10;            app.MapGet("/", todoController.GetTodos);&#10;            app.MapPost("/api/todos", todoController.CreateTodo);&#10;            app.MapPost("/api/todos/{id}", todoController.ToggleTodo);&#10;            app.MapDelete("/api/todos/{id}", todoController.DeleteTodo);&#10;&#10;            await app.RunAsync();&#10;        }&#10;    }&#10;}</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <b>Convention Over Configuration</b> </p> <p style="margin-left: auto; margin-right: auto;">در کد قبلی به صورت دستی TodoController را توسط Service Location از DI درخواست کرده‌ایم. اینکار را در ادامه می‌توانیم به Feather HTTP سپرده تا کار وهله‌سازی را براساس قواعد توکار برایمان انجام دهد:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using System.Threading.Tasks;&#10;using Microsoft.AspNetCore.Builder;&#10;using Microsoft.AspNetCore.Http;&#10;using Microsoft.Extensions.DependencyInjection;&#10;using todoAPI.Services;&#10;&#10;namespace todoAPI&#10;{&#10;    class Program&#10;    {&#10;        static async Task Main(string[] args)&#10;        {&#10;            var builder = WebApplication.CreateBuilder(args);&#10;&#10;            builder.Services.AddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;();&#10;&#10;            builder.Services.AddControllers();&#10;&#10;            builder.Services.AddSingleton&lt;ITodoService, TodoService&gt;();&#10;&#10;            var serviceProvider = builder.Services.BuildServiceProvider();&#10;&#10;            var app = builder.Build();&#10;&#10;            app.MapControllers();&#10;&#10;            await app.RunAsync();&#10;        }&#10;    }&#10;}</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">سپس در ادامه برای دسترسی به HTTP Context درون TodoController از <a href="https://www.dntips.ir/post/2554">IHttpContextAccessor</a> استفاده کرده‌ایم: </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">using System.Threading.Tasks;&#10;using Microsoft.AspNetCore.Http;&#10;using Microsoft.AspNetCore.Mvc;&#10;using todoAPI.Models;&#10;using todoAPI.Services;&#10;&#10;namespace todoAPI.Controllers&#10;{&#10;    public class TodoController&#10;    {&#10;        private readonly ITodoService _todoService;&#10;        private readonly IHttpContextAccessor _accessor;&#10;        public TodoController(ITodoService todoService, IHttpContextAccessor accessor)&#10;        {&#10;            _todoService = todoService;&#10;            _accessor = accessor;&#10;        }&#10;&#10;        [HttpGet("/todos")]&#10;        public async Task GetTodos()&#10;        {&#10;            var todos = _todoService.GetAllToDoItmes();&#10;            await _accessor.HttpContext.Response.WriteJsonAsync(todos);&#10;        }&#10;&#10;        [HttpPost("/todos")]&#10;        public async Task CreateTodo()&#10;        {&#10;            var todo = await _accessor.HttpContext.Request.ReadJsonAsync&lt;Todo&gt;();&#10;            _todoService.AddTodo(todo);&#10;            _accessor.HttpContext.Response.StatusCode = 204;&#10;        }&#10;&#10;        [HttpPost("/todos/{id}")]&#10;        public async Task ToggleTodo(int id)&#10;        {&#10;            _todoService.ToggleTodo(id);&#10;            _accessor.HttpContext.Response.StatusCode = 204;&#10;        }&#10;&#10;        [HttpDelete("/todos/{id}")]&#10;        public async Task DeleteTodo(int id)&#10;        {&#10;            _todoService.DeleteTodo(id);&#10;            _accessor.HttpContext.Response.StatusCode = 204;&#10;        }&#10;    }&#10;}</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">کدهای کامل مطلب را می‌توانید از <a href="https://github.com/SirwanAfifi/todoAPI">اینجا</a> دریافت کنید.</p> </div></div>
