پیاده سازی یک Web Api ساده با Asp.Net Web Forms

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) را انتخاب نمایید. نام پروژه را طبق سلیقه خود در بخش مربوطه وارد کرده و پوشه ای را که می خواهید پروژه در آن پوشه ایجاد و ذخیره گردد را نیز در بخش مربوطه وارد نمایید.

CreateAsp.NetWebApplication

ما اسم پروژه خود را 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 قرار می دهیم تا کلاس مورد نظر به پروژه اضافه شود.

Web Api Controller  

کد داخل کلاس را به شکل زیر تغییر میدهیم:

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 را به انتهای آدرس اصلی که در مرورگر قرار دارد اضافه می کنیم  و با مرور گر فراخوانی می کنیم. اگر همه چیز درست باشد نتیجه حاصله چنین خواهد بود:

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 DefaultAspx  

حال یک سوال در ذهن ما پدید می آید که برنامه ما با فراخوانی آدرس /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 زیر منطبق بر قالب مسیر پیش فرض نیست:
  • /contacts/1
زیرا بخش 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 خطای زیر را دریافت خواهیم کرد:

Multiple Actions Error  

تغییرات در مسیریابی :
توضیحات ارایه شده مرتبط با مکانیزم مسیریابی ابتدایی در 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



آخرین بروزرسانی
۱۶ اسفند ۱۴۰۲ 
تعداد کلیک
۱۰,۳۶۵

فهرست نظرها و ارسال نظر جدید

نام را وارد کنید
ایمیل را وارد کنید
تعداد کاراکتر باقیمانده: 1000
نظر خود را وارد کنید