Web Api چیست؟ قسمت دوم
پیاده سازی یک Web Api ساده با Asp.Net Web Forms
می خواهیم اولین Web Api خود را در محیط Asp.net Web Forms پیاده سازی کنیم، خواهیم دید که Visual Studio بسیاری از کارها را برای ما راحت کرده است و با چند خط کد نا قابل اولین و کوچکترین Web API خود را خواهیم ساخت و آن را
کمی گسترش خواهیم داد تا بخش ها و مفاهیم بیشتری را پوشش دهیم.
برای این کار ما از Visual Studio 2015 استفاده می کنیم.
Visual Studio را اجرا کنید. و از منوی File ، سپس منوی New ، منوی
Project را انتخاب نمایید تا بتوانیم یک پروژه جدید ایجاد کنیم. یک صفحه
برای ما باز خواهد شد. بمانند شکل زیر، ابتدا منوی Installed را از سمت چپ
انتخاب نمایید و سپس زیر منوی Templates و سپس زیر منوی Visual C#
و سپس زیر منوی Web را انتخاب نمایید. از میان موارد لیست شده در
باکس وسط، گزینه Asp.Net Web Application(.Net FrameWork) را انتخاب
نمایید. نام پروژه را طبق سلیقه خود در بخش مربوطه وارد کرده و پوشه ای را
که می خواهید پروژه در آن پوشه ایجاد و ذخیره گردد را نیز در بخش مربوطه
وارد نمایید.
ما اسم پروژه خود را GettingStartedWebApi قرار داده ایم.
حال باید مدل و کنترلر خود را ایجاد کنیم، منظور از مدل همان مدل داده ای
ماست و کلاسی است که برای ایجاد شیء مورد نظر خود ایجاد می کنیم، برای این
کار یک کلاس ایجاد کرده و نام آن را Product قرار می دهیم، یعنی مدل داده
ای ما یک Product با مشخصه های اصلی مانند کد، نام ، قیمت و ... است. کلاس
مورد نظر به شکل زیر خواهد بود:
namespace GettingStartedWebApi
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
}
حال باید یک کنترل کننده Web Api به پروژه خود اضافه کنیم. شاید
بتوان گفت این کلاس وظیفه اجرای درخواست های رسیده شده را به عهده دارد. در
اصل یک Controller شیء ایست که وظیفه رسیدگی به درخواست های HTTP
رسیده شده به Web Api را به عهده دارد.
برای اضافه کردن یک Controller به پروژه خود چنین عمل می کنیم :
روی اسم پروژه کلیک راست کرده و سپس منوی ADD و سپس منوی New Item
را کلیک می کنیم تا پنجره لیست های قابل افزودن به پروژه ظاهر شود. از
منوی سمت چپ، منوی Installed و سپس منوی
Visual C# و سپس منوی وب را
انتخاب می کنیم، و سپس از میان موارد لیست شده Web Api Controller
Class را انتخاب می کنیم و اسم آن را نیز ProductsController.cs قرار
می دهیم تا کلاس مورد نظر به پروژه اضافه شود.
کد داخل کلاس را به شکل زیر تغییر میدهیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;
namespace GettingStartedWebApi
{
public class ProductsController : ApiController
{
Product[] products = new Product[]
{
new Product {
Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product {
Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product {
Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M },
new Product {
Id = 4, Name = "Potato", Category = "Groceries", Price = 11 },
new Product {
Id = 5, Name = "Carrot", Category = "Groceries", Price = 12 },
};
public IEnumerable<Product>
GetAllProducts()
{
return
products;
}
public Product GetProductById(int id)
{
var product =
products.FirstOrDefault((p) => p.Id == id);
if (product
== null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return
product;
}
public IEnumerable<Product>
GetProductsByCategory(string category)
{
return
products.Where(
(p) => string.Equals(p.Category, category,
StringComparison.OrdinalIgnoreCase));
}
}
}
حال باید کد های مربوط به مسیر یابی را ایجاد کنیم تا درخواست
های رسیده شده به سمت این کنترلر هدایت شوند. برای این کار باید
یک قطعه کدی را به فایل Global.asax اضافه کنیم ، لطفا فایل حاوی کد
مربوطه به نام Global.asax.cs را باز کنید و کد زیر را به متد
Application_Start آن
اضافه کنید:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional
}
);
کد زیر را نیز فراموش نکنیم که به ابتدای فایل اضافه کنید:
using System.Web.Http;
حال باید یک صفحه وب ایجاد کنیم تا بتوانیم از طریق Ajax متد های داخل
Controller را فراخوانی کنیم، یعنی در اصل Web Api ایجاد شده را اجرا
کنیم.
از طریق منوی مربوطه صفحه default.aspx را ایجاد می کنیم.
اما قبل از اینکه متد خود را از طریق jQuery فراخوانی کنیم، می خواهیم
اطمینان حاصل کنیم که کد ما درست کار می کند، بنابراین با یک ابزار
دیگری باید Web API خود را فراخوانی کنیم. همانطور که می دانید می
توانیم با استفاده از مرورگرهای داخل کامپیوتر خود این کار را انجام
دهیم. کافیست که پروژه خود را اجرا کنیم تا Web APIما توسط IIS Express
تعبیه شده داخل VS میزبانی شود. بعد از اینکه پروژه اجرا شد، صفحه
مرورگر پیش فرض ما باز می شود. حال آدرس /api/products را به
انتهای آدرس اصلی که در مرورگر قرار دارد اضافه می کنیم و با
مرور گر فراخوانی می کنیم. اگر همه چیز درست باشد نتیجه حاصله چنین
خواهد بود:
همانطور که می بینید نتیجه به صورت XML در مرورگر به نمایش در آمده
است، بنا براین نتیجه می گیریم که Web API نوشته شده به صورت صحیح کار
می کند.
حال می توانیم از
طریق Package Manager ، جی کوئری را دانلود کنیم تا بتوانیم از
کتابخانه آن برای کاری که در پیش داریم استفاده کنیم، اما حتما نیاز
نیست این کار را انجام دهیم، کافی است لینک آنلاین آن را به
عنوان Reference در صفحه خود قرار دهیم که در ادامه خواهیم دید. کد
داخل صفحه default.aspx چنین خواهد بود.
<asp:Content ID="BodyContent"
ContentPlaceHolderID="BodyContentPlaceHolder" runat="server">
<script src="https://code.jquery.com/jquery-1.10.2.min.js"
type="text/javascript"></script>
<h2>Products</h2>
<table style="border: 1px solid #000000;">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody id="products">
</tbody>
</table>
<script type="text/javascript">
function getProducts() {
$.getJSON("/api/products",
function
(data) {
$('#products').empty(); // Clear the table body.
// Loop through the list of products.
$.each(data, function (key, val) {
// Add a table row for the product.
var row = '<td>' + val.Name + '</td><td>' + val.Price + '</td>';
$('<tr/>', { html: row }) // Append the name.
.appendTo($('#products'));
});
});
}
$(document).ready(function () {
// we call
the function
getProducts();
});
</script>
</asp:Content>
به کامنت ها نیز نگاهی بیاندازید تا وظیفه هر بخش از کد را بدانید.
همانطور که می بینید در متد
$(document).ready یک تابع را
فراخوانی کرده ایم. تابع getProducts وظیفه اتصال به WebApi نوشته شده
ما را به عهده دارد که برای این کار از کتابخانه آژاکس jQuery استفاده
می کند. متد getJSON یک درخواست HTTP به فعل GET به
WEBAPI ما ارسال کرده و نتیجه را در قالب json دریافت میکند. بعد
متد each نیز داخل آبجکت json دریافت شده از API چرخیده و نام و قیمت
هر محصول را داخل یک سطر به ما نمایش می دهد. توجه داشته باشید که
عبارت های استفاده شده ی val.Name و val.Price دقیقه منطبق با نامگذاری
ما در کلاس Product است. حال اگر صفحه default.aspx را فراخوانی
کنیم نتیجه زیر را خواهیم دید:
حال یک سوال در ذهن ما پدید می آید که برنامه ما با فراخوانی آدرس
/api/products چگونه فهمید که باید کدام متد از کدام کلاس را اجرا کند؟
همانطور که اشاره کردیم، در Asp.Net Web API یک کنترلر کلاسی است که به
درخواست های HTTP رسیدگی می کند. متد های Public که در Controller
نوشته می شوند به نام Action Method شناخته می شوند که معمولا برنامه
نویسان به آنها Action می گویند. زمانی که بدنه () Web API یک
درخواست دریافت می کند، این درخواست را به سمت یک Action مشخص هدایت می
کند. برای مشخص کردن اینکه کدام Action باید فراخوانی شود توسط routing
table در بدنه Web API انجام می گیرد. ویژوال استودیو یک route پیش فرض
برای استفاده Web API ایجاد می کند که در بالا کد مربوطه را آورده ایم.
هر آیتم در routing table شامل یک route template است. route
template پیش فرض برای Web API ، قالب مسیر
api/{controller}/{id} است که در کد بالا نیز از
همین قالب مسیر استفاده کردیم.
در این قالب پیش فرض عبارت api ، بخش ثابت مسیر است که شاید بتوان گفت
یک نوع قرار داد در Web API باشد. و {Controller} و {id} متغیر
های جایگزین شونده هستند که بنا بر نیاز سرویس با عبارات مشخصی جایگزین
می شوند.
وقتی بدنه Web API یک درخواست دریافت می کند، تلاش می کند تا قالب
منطبق و مربوطه را در جدول Route ها پیدا کند. و اگر قالب منطبق به
درخواست را در لیست route ها پیدا نکرد خطای 404 را برای سرویس گیرنده
ارسال خواهد کرد. URI های زیر منطبق بر قالب مسیر پیش فرض هستند:
- /api/contacts
- /api/contacts/1
- /api/products/gizmo1
اما
URI زیر منطبق بر قالب مسیر پیش فرض نیست:
زیرا بخش api در URI اشاره شده وجود ندارد، بنابراین استاندارد مورد
نظر را رعایت نکرده است.
دلیل استفاده از قرار داد وجود api در ابتدای همه قالب مسیر ها این است
که با مسیر یابی موجود در Asp.Net MVC تداخل ایجاد نشود، بنابرین
اگر api را از ابتدای URI حذف کنیم، بدنه WEB API سراغ MVC controller
خواهد رفت. اگر کسی بخواهد قالب مسیر دیگری به جای قالب پیش فرض داشته
باشد میتواند با رعایت استاندارد های لازم، قالب مسیر خودش را ایجاد
کند.
به مجرد اینکه قالب مسیر منطبق با درخواست از جدول مسیر ها یافت
شد،WEB API ، کنترلر (controller) و متد (action) مربوطه را انتخاب
خواهد کرد:
- برای یافتن contoller مورد نظر،WEB API ، عبارت Controller را
به انتهای مقدار {controller} اضافه می کند، به عنوان مثال اگر URI ای
که فراخوانی می شود /api/products باشد، WebAPI به دنبال
ProductsController خواهد گشت و کلاس مرتبط و منطبق با این نام کلاس
هدف خواهد بود
- برای پیدا کردن متد (action) مرتبط WebAPI به فعل فراخوانی شده توسط
HTTP نگاه می کند، یعنی بررسی می کند که
HTTP METHOD
فراخوانی شده Get، PUT، DELETE و یا مثلا POST است، سپس بررسی می کند
که کدام متد با کلمه GET شروع شده
است و یا کدام متد با کلمه Post شروع
شده است.این قرار داد فقط برای افعال/متد های GET/POST/PUT/DELETE تعریف شده است.
ما بقی عبارات قرار داده شده در URI به پارامتر های مورد نیاز متد یافت
شده تخصیص داده خواهد شد، یعنی در URI اشاره شده /api/product/1 متد
یافت شده یک پارامتر نیز دارد و متد مثلا چنین خواهد بود:
public Product GetProductById(int id) { }
به یک مثال اشاره می کنیم. فرض می کنیم که Controller تعریف شده ما
چنین باشد:
public class ProductsController : ApiController
{
public IEnumerable<Product> GetAllProducts() { }
public Product GetProductById(int id) { }
public HttpResponseMessage DeleteProduct(int id){ }
}
حال میتوانیم لیستی از URI Path ها و HTTP Method ها و Action ها و
پارامتر ها را در جدول زیر بیاوریم که برخی از آنها به صورت صحیح
با استاندارد موجود منطبق هستند و برخی نه.
HTTP Method |
URI Path |
Action |
Parameter |
GET |
api/products |
GetAllProducts |
(none) |
GET |
api/products/4 |
GetProductById |
4 |
DELETE |
api/products/4 |
DeleteProduct |
4 |
POST |
api/products |
(no match) |
|
همانطور که مشاهده می کنید در متد های بالا ما دو متد داریم که با
عبارت Get شروع می شود که بسته به اینکه در URI فراخوانی شده
پارامتر داشته باشیم یا نه action مربوطه انتخاب می شود. یعنی اگر
URI ما api/products باشد متد GetAllProduct فراخوانی می شود
و اگر URI ما /api/products/4 باشد ، متد GetProductById فراخوانی
می شود.
در نظر داسته باشید کلیه درخواست های POST همگی با خطا مواجه
خواهند شد، چون هیچ متدی وجود ندارد که با عبارت Post شروع شده
باشد.
مسیر یابی توضیح داده شده مسیر یابی پیش فرض تعبیه شده در WEBApi
است که خود یک محدودیت های دارد که به آنها اشاره می کنیم. به
عنوان مثال غیر از متد GetAllProduct تعریف شده می خواهیم متد
دیگری نیز داشته باشیم که لیست متفاوت تری از محصولات را به ما
برگرداند، به عنوان مثال ، محصولاتی که بیشترین فروش را داشته اند،
یا محصولاتی که کاربران برای آنها نظری نوشته اند. مثلا متد زیر:
public IEnumerable
GetSomeProducts()
{
return products;
}
در این صورت بعد از Build مجدد و فراخوانی URI مربوطه
/api/products خطای زیر را دریافت خواهیم کرد:
تغییرات در مسیریابی :
توضیحات ارایه شده مرتبط با مکانیزم مسیریابی ابتدایی در Web API
بود، در WEB API برای تغییر و تکمیل آن راه حل دیگری وجود دارد که
به بررسی آن می پردازیم:
به جای استفاده از قرار داد های نامگذاری پیش فرض می توانیم
به صراحت مشخص کنیم که کدام HTTP Method برای کدام Action اعمال می
گردد. این کار با تخصیص عبارت های HttpGet ، HttpPut ، HttpPost و
HttpDelete به action های داخل کلاس انجام می گیرد.
در مثال زیر متد FindProduct به درخواست های Get تخصیص داده شده
است.
public class ProductsController : ApiController
{
[HttpGet]
public Product FindProduct(id) {}
}
برای تخصیص چند HTTP Method برای یک action و یا تخصیص HTTP
Method هایی غیر از GET ، PUT ، POST و DELETE به یک Action از
عبارت AcceptVerbs استفاده می کنیم که لیستی از HTTP Method ها
را میتوانیم به عنوان ورودی آن در نظر بگیریم.
مانند مثال زیر:
public class ProductsController : ApiController
{
[AcceptVerbs("GET", "HEAD")]
public Product FindProduct(id) { }
// WebDAV method
[AcceptVerbs("MKCOL")]
public void MakeCollection() { }
}
مسیریابی یا استفاده از نام action:
می توان با تغییر دادن یا ایجاد یک مورد در جدول Route ها
اسم action را نیز در URI مورد نظر برای فراخوانی قرار
دهیم. به کد زیر توجه کنید که می توانیم به Global.ascx
اضافه کنیم.
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
در این نوع قالب مسیر یابی عبارت {action} مشخص می کند
که دقیقا کدام action از Controller ما باید فراخوانی
شود. فرض می کنیم که متد زیر را در Controller داشته
باشیم:
public class ProductsController : ApiController
{
[HttpGet]
public string Details(int id);
}
در این صورت با فراخوانی یک HTTP GET با
استفاده از URI روبرو:
"api/products/details/1" متد Details فراخوانی
خواهد شد. این مدل Routing مشابه با مسر یابی در
MVC است و مناسب API های منطبق بر RPC-style است.
همجنین ما می توانیم اسامی جدیدی را نیز برای
action ها تخصیص دهیم، برای این کار از عبارت
ActionName استفاده می کنیم. به عنوان نمونه، در
مثال زیر، دو action وجود دارد که به
URI روبرو api/products/thumbnail/id
اشاره می کنندو یکی از آنها از Get و دیگری از
Post پشتیبانی می کند. یعنی یکی مسئول پاسخگویی به
درخواست Get و دیگری مسئول پاسخگویی به درخواست
Post است:
public class ProductsController : ApiController
{
[HttpGet]
[ActionName("Thumbnail")]
public HttpResponseMessage GetThumbnailImage(int id);
[HttpPost]
[ActionName("Thumbnail")]
public void AddThumbnailImage(int id);
}
برای جلوگیری از فراخوانی یک action از طریق URI
ها هم کافیست در بالای متد عبارت NonAction را
قرار دهیم.این کار به فریم وورک می گوید که متد
action نیست، حتی اگر URI ای منطبق با این action
فراخوانی شود:
// Not an action method.
[NonAction]
public string GetPrivateData() { ... }
منابع:
ASP.NET Web API
ASP.NET MVC - Web API