كيفية استدعاء Web API في ASP.NET Core

-

تعلمنا سابقا كيفية إنشاء Web APIs  في ASP.NET Core [نمط RESTful] حيث قمنا  بإنشاء Web API لقراءة المواد من قاعدة البيانيات وأيضا انشانا API لإضافة مادة جديدة.

لكن كيف يمكن نستفيد من هذا API وكيف ممكن نستخدمو.

سنقوم الآن باستدعاء (API) في مشروع آخر. 

سنعمل على انشاء مشروع جديد خاص للطلاب بنسميه CustomerPortal

يمكن للطالب في هاذ Portal من عرض المواد الي بتكون موجوده ومتوفرة. 


للتذكير ايش هي API؟

API تعني (Application Programming Interface) وهي واجهة او طريقة يمكن من خلالها للعديد من التطبيقات بالتواصل مع بعضها البعض.  نستخدم API يوميًا مثل:

تحديث الطقس الحالي لمنطقتنا على الهاتف.

وأسعار الأسهم الحالية ، وما إلى ذلك.

تطبيقات حجز السفر ، من خلال هذه التطبيقات ممكن نوصل لأكثر من شركة طيران.

بنقدر نقول ان API  ممكن نوصل ليها عن طريق عن URL 


الان نوخذ فكرع عن شو هو استدعاء API؟

API هو خدمه تم برمجتها بحيث يؤدي العمل الذي تم تصميمه من أجله. يعني بنطلب API حتي نحصل على نتيجة، بعد ما نطلب API بتكون النتيجة اذا تم التنفيذ بيرسل حالة العمل المكتمل ، في حالة حدوث خطأ ، يتم إرجاع رمز الخطأ المناسب والرسالة مرة أخرى(اذا بتذكر حكينا سابقا عن رموز الأخطاء الىي ممكن ترجع).


حتى نستخدم هذا API لازم نعرف وين مكان الاستخدام المناسب. إذا كنت مطور تطبيقات اكيد الاستخدام بكون داخل تطبيقك، إذا كنت مستخدم عادي بكون حسب الخدمة المطلوبة (ارجع للأمثلة السابقة)

وبما ان الهدف في هذا الدرس نتعلم كيف ممكن نستخدم API في التطبيقات، لذا سنبدأ بإنشاء مشروع جديد 


ملاحظة :

حكينا بالسابق عن ان API يعمل مع الطرق التي تستدعيها HTTP protocols. وهاذي الطرق  يطلق عليها GET ، PUT ، POST ، و DELETE وهي الأكثر شيوعًا.

خلينا نبدأ بإنشاء مشروع جديد في 2022 Visual Studio  ونسميه StudentPortal ويكون نوع المشروع 

Model-View-Controller))ASP.Net Core Web App  )

بنستخدم تصميم MVC في هذا المشروع ، الهدف عرض البيانات الي بترجع من API في View، واكيد لازم تكون لغة البرمجة #C بعدها Next 

من الشاشة التالية نختار اسم المشروع StudentPortal ومكان التخزين وبعدها Next ، 

بعدها نختار Framework والي بتكون 6.0 وما بنغير اشي من باقي الإعدادات واكيد بعدا نختار  Create 

ننتظر شوي حتي يتم انشاء التطبيق وبتكون النتيجة 

طيب الأمور لحد الان تمام. 

الان صار دور نفكر بشو بدنا نعرض في الصفحة View ، وبما ان الهدف نستخدم API الي عملنا سابقا وكان برجع المواد المتوفرة يعني الهدف استدعاء هذا API وعرض المواد في View

تمام بناء على هذا الكلام نحنا بحاجة نعمل model حتي نستقبل البيانات الي بترجع 

طيب ننقل الى مجلد Models وخلينا نضيف class باسم CoursesModel  وبنظيف الكود التالي اليه  

public class CoursesModel
    {
        public int CoursesId { get; set; }
        public string CourseNumber { get; set; }
        public string CourseName { get; set; }
        public string CourseDescription { get; set; }
        public decimal Price { get; set; }
        public int Capacity { get; set; }
    }
تمام ان شاء الله الامور تكون واضحة لحد الان. 
تأكد من المشروع يكون فيه ملف في مجلد Share باسمViewImports.cshtml_ ، للمزيد حول هذا يمكن الرجوع الى الدرس (رابط الدرس)
واكيد نتأكد يكون الكود التالي موجود في الملف : 
@using StudentPortal
@using StudentPortal.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

طيب اكيد نحتاج مجموعة من package الي بنزلها من package  NuGet ، وحتي نوصل الها اضغط يمين فوق المشروع وبعدها اختار 

لحد اللحظة نحتاج Newtonsoft.Json . طيب تمام الان نروح على Browes وبندخل اسم Newtonsoft.Json حتي نبحث عنها ونثبتها (شوف الصورة)


الهدف من هاذي Pakage : 
تستخدم لللاتصال ب Web API ، باستخدام HttpClient() class وهي شائعة جدًا. وبنستخدما كمان حتي نعمل Deserialize JSON الى Coursemodel class .
الهدف من Deserialize JSON  هو قراءة البيانات من Model امبعوثة عن طريق API 

بعد ما نثبتها بنجاح وحتي نتأكد انها نزلت ممكن نشوف Pakages في المشروع شوف الصورة 


لحد الان امورنا تمام 

خلينا نراجع بعض الاوامر الموجوده في HttpClient  وشو الهدف من كل وحده:
الطريقة    الوصف
GetAsync Send a GET request to the specified Uri as an asynchronous operation.
PostAsync Send a POST request to the specified Uri as an asynchronous operation.
PutAsync Send a PUT request to the specified Uri as an asynchronous operation.
SendAsync Send an HTTP request as an asynchronous operation.
PatchAsync Sends a PATCH request with a cancellation token as an asynchronous operation.
DeleteAsync Send a DELETE request to the specified Uri as an asynchronous operation.

طيب نحنا الان جاهزين نبدا بقراءة البيانات من API 

مهم جدا نعرف ان API Web تحتوي على HttpGet method تُرجع جميع البيانات على شكل JSON. خلينا نرجع للكود الخاص بالمواد الي كان .

public async Task<ActionResult<IEnumerable<CoursesModel>>> GetCoursesModel()

شوف ان الداتا الي بترجع من نوع CoursesModel على شكل IEnumerable

تمام التمام. 
.
لقراءة جميع سجلات المواد لازم نعمل طلب من نوع HTTP GET ل API. مشان هيك ، خلينا نبدأ بعمل Controller  داخل مجلد Controllers وقم بتسميتها CourseController.cs.
ملاحظة : بنستخدم MVC Controller  وليس API Controller 
شوف الصورة (استخدمنا MVC Controller Empty) 


مام الان صار الوقت نضيف الكود حتي نقرا Web API ، بنضيف الكود التالي الى Controller 
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using StudentPortal.Models;
namespace StudentPortal.Controllers
{
    public class CourseController : Controller
    {
        public async Task<IActionResult> Index()
        {
            List<CoursesModel> CoursesList = new List<CoursesModel>();
            using (var httpClient = new HttpClient())
            {
                using (var response = await httpClient.GetAsync("https://localhost:7081/api/Course/GetAllCourses"))
                {
                    string apiResponse = await response.Content.ReadAsStringAsync();
                    CoursesList = JsonConvert.DeserializeObject<List<CoursesModel>>(apiResponse);
                }
            }
return View(CoursesList);
        }
    }
}
تمام لاحظ الكود
List<CoursesModel> CoursesList = new List<CoursesModel>();

 عرفنا List من نوع CoursesModel حتي نخزن البيانات فيها. 

بعدها استخدمنا الامر 
 
var response = await httpClient.GetAsync("https://localhost:7081/api/GetAllCourses")

الهدف قراءة البيانات من Web API باستخدام httpClient.GetAsync الى هو مشتق من System.Net.Http namespace 

لاحظ كمان ان Index Action من نوع async يعني asynchronous  والسبب ان HttpClient class تقدم طلبًا غير متزامن asynchronous فقط والذي يمكن أن يحدث فقط من طريقة إجراء غير متزامن asynchronous action method. واكيد بنستخدم await  مع async
وطبعا بما ان احنا شغالين Local اكيد بكون الرابط Local ( الرابط متغير حسب API)، بعدها عرفنا متغير باسم apiResponse بحتوي على محتويات APIباستخدام response.Content.ReadAsStringAsync()، وحسب ما تعلمنا وعرفنا ان API برجع الداتا على شكل JSON، بعدها لازم نعمل Deserialize لهاي الداتا، بعدها استخدمنا الامر
 JsonConvert.DeserializeObject<List<CoursesModel>>(apiResponse);
 وعملنا Deserialize حتي نرجع البيانات ونخزنها في CoursesList 

اخر سطر رجعنا الداتا الي تخزنت في المتغير CoursesList الى View،
تمام خلينا نحاول نشغل التطبيق، ستكون النتيجة خطأ :(لاحظ الصورة )


السبب في هذا الخطا ان بعد ما عملنا View لهذا Action 
 
طيب الان صار دور نعمل View حتي نعرض البيانات، ولعمل View المطلوب انقر يمين فوق index action في ال controlloer وبعدها اخترا Add View


وبعدها بنختار 

خلينا نحاول نشغل التطبيق الان بالدخول الى الرابط   :
https://localhost:7242/Course
ملاحظة : يما ان الشغل local ، فا لازم يكون المشروع تبع API شغال حتي يتم قراءة البيانات بشكل صحيح
نتيجة التنفيذ بتكون :

لاحظ ان ما في اي بيانات رجعت، للتاكد من ان الشغل صح وما في مشاكل خلينا نعمل BreakBoint في index action ونشوف النتائج (شوف الصورة)




بتلاحظ ان في بيانات راجعه من API يعني لحد الان الشغل صح.تمام طيب شو هو السبب ان ما في اي بيانات رجعت في View ، السبب ان لازم نعمل اكواد في View لعرض البيانات. 
الان بنظيف الكود التالي الى View
@model IEnumerable<CoursesModel>
@{ Layout = "_Layout"; ViewBag.Title = "All Courses";}
 
<h2>All Courses</h2>
<a asp-action="AddCourse" class="btn btn-sm btn-primary">Add Courses</a>
<a asp-action="GetCourse" class="btn btn-sm btn-secondary">Get Courses</a>
 
<table class="table table-sm table-striped table-bordered m-2">
    <thead>
        <tr>
             <th>Course ID</th>
            <th>CourseNumber</th>
            <th>CourseName</th>
            <th>CourseDescription</th>
            <th>Price</th>
            <th>Capacity</th>
            <th>Update</th>
            <th>Delete</th>
        </tr>
</thead>
    <tbody>
        @foreach (var C in Model)
        {
            <tr>
                <td>@C.CoursesId</td>
                <td>@C.CourseNumber</td>
                <td>@C.CourseName</td>
                <td>@C.CourseDescription</td>
                <td>@C.Price</td>
                <td>@C.Capacity</td>
                <td>
                    <a asp-action="UpdateReservation" asp-route-id="@C.CoursesId">Edit
                        @*<img src="/icon/edit.png" />*@
                    </a>
                </td>
                <td>
<form asp-action="DeleteReservation" method="post">
                        <input type="hidden" value="@C.CoursesId" name="CoursesId" />
                        <input type="button" value="Delete" class="btn btn-danger">
                       @* <input type="image" src="/icon/close.png" />*@
                    </form>
                </td>
            </tr>
        }
    </tbody>
</table>
وبعدها خلينا نحاول نشغل المشروع بالدخول الى الرابط : 
https://localhost:7242/Course 
الان اكيد لازم تكون امورنا تمام التمام وبتكون النتيجة (الصورة تحت)
حلو الامور تمام وان شاء الله تكون الامور معك ماشيه. 
نرجع نشرح الكود الي عملناه 
اول View اضفنا الكود
 @model IEnumerable<CoursesModel>
وهذا يعني ان في هذا View حنستقبل Object  من نوع CoursesModel على شكل IEnumerable 
بعدها الكود : 
<a asp-action="AddCourse" class="btn btn-sm btn-primary">Add Courses</a>
<a asp-action="GetCourse" class="btn btn-sm btn-secondary">Get Courses</a>

روابط في الصفحة الاول لاضافة كورس والثاني حتي نرجع تفاصيل الكورس ، (لاحظ الامر asp-action الي بيعني ان هذا الكود حيستدعي action الى موجود بعدو ) وهنا بنستدعي action الاول للاضافة AddCourse  والثاني حتي نرجع التفاصيل GetCourse

بعدها اضفنا كود HTML , وعرفنا  <thead>  ل Header واضفنا اسماء الحقول الي مطلوب ترجع وهي 

 <thead>
        <tr>
             <th>Course ID</th>
            <th>CourseNumber</th>
            <th>CourseName</th>
            <th>CourseDescription</th>
            <th>Price</th>
            <th>Capacity</th>
            <th>Update</th>
            <th>Delete</th>
        </tr>
    </thead>

بعدها استخدمنا  كود foreach حتي نقرا البيانات من Object  الي هو CourseModel الى استقبلنا في View، لاحظ ان في هذا foreach  اضفنا ايضا 2 action الاول للتعديل والثاني للحذف.
وبهيك بنكون اتممنا قراءة البيانات من API وعرضها على View . 

طيب الان خلينا نعمل action حتي نرجع البيانات بناء على رقم الكورس (في Web API في الدرس السابق عملنا API لقراءة بيانات المواد حسب ID ) . 
اولا: المطلوب اضافة action  جديد في course controllor لارجاع بيانات المواد بناء على ID، وبكون اسم هذا Action  هو 

public ViewResult GetCourseByID() => View();
        [HttpPost]
        public async Task<IActionResult> GetCourseByID(int id)
        {
            List<CoursesModel> CoursesList = new List<CoursesModel>();
            using (var httpClient = new HttpClient())
            {
                using (var response = await httpClient.GetAsync("https://localhost:7081/api/Course/GetCoursesById?id=" + id))
                {
                    if (response.StatusCode == System.Net.HttpStatusCode.OK)
                    {
                        string apiResponse = await response.Content.ReadAsStringAsync();
                        CoursesList = JsonConvert.DeserializeObject<List<CoursesModel>>(apiResponse);
                    }
                    else
                        ViewBag.StatusCode = response.StatusCode;
                }
            }
            return View(CoursesList);
        }
لاحظ أضفنا 2 action هنا الاول HTTP GET والثاني  HTTP POST. 
الهدف منهم الاول  HTTP GET يعمل ببساطة على إرجاع default view، بينما الثاني HTTP POST يعمل على استدعاء Web API وتزويده برقم الكورس والتي  ستعيد تفاصيل الكورس حسب Id.
اضافة View 
انقر فوق action يمين ثم اختر  Add View ثم اضف الكود التالي الى هذا View
@model IEnumerable<CoursesModel>
@{ Layout = "_Layout"; ViewBag.Title = "Get Course by Id";}
 
<h2>Get Course by Id </h2>
<h3>@ViewBag.StatusCode</h3>
<form method="post">
    <div class="form-group">
        <label for="id">Course Id:</label>
        <input class="form-control" name="id" />
          
    </div>
    <hr />
    <div class="form-group">
      <button type="submit" class="btn btn-sm btn-secondary">Get Course Details</button>
    </div>
</form>
@if (Model != null)
{
    <h2>Course Details</h2>
  
<table class="table table-sm table-striped table-bordered m-2">
    <thead>
        <tr>
             <th>Course ID</th>
            <th>CourseNumber</th>
            <th>CourseName</th>
            <th>CourseDescription</th>
            <th>Price</th>
            <th>Capacity</th>
            <th>Update</th>
            <th>Delete</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var C in Model)
        {
            <tr>
                <td>@C.CoursesId</td>
                <td>@C.CourseNumber</td>
                <td>@C.CourseName</td>
                <td>@C.CourseDescription</td>
                <td>@C.Price</td>
                <td>@C.Capacity</td>
                <td>
                    <a asp-action="UpdateReservation" asp-route-id="@C.CoursesId">Edit
                        @*<img src="/icon/edit.png" />*@
                    </a>
                </td>
<td>
                    <form asp-action="DeleteReservation" method="post">
                        <input type="hidden" value="@C.CoursesId" name="CoursesId" />
                        <input type="button" value="Delete" class="btn btn-danger">
                       @* <input type="image" src="/icon/close.png" />*@
                    </form>
                </td>
            </tr>
        }
    </tbody>
</table>
}
بتلاحظ ان الكود هو نفس الكود السابق

تشغيل التطبيق :
انتقل الى الرابط التالي:

 "https://localhost:7242/Course/GetCourseByID"

ستكون نتيجة التنفيذ:

سننتقل الان الى استدعاء API بغرض اضافة مادة جديده. 
اولا: أضف الكود التالي الى Controller
public ViewResult AddCourses() => View();
        [HttpPost]
        public async Task<IActionResult> AddCourses(CoursesModel coursesModel)
        {
            CoursesModel receivedCoursesModel = new CoursesModel();
            using (var httpClient = new HttpClient())
            {
                StringContent content = new StringContent(JsonConvert.SerializeObject(coursesModel), Encoding.UTF8, "application/json");
                using (var response = await httpClient.PostAsync("https://localhost:7081/api/Course/AddCourses", content))
                {
                }
            }
           return Redirect("Index");
        }
كما فعلنا في action السابق تم إضافة نوعين  2 action هنا الاول HTTP GET والثاني  HTTP POST.
الفرق هنا ارسلنا object  من نوع CoursesModel يحتوي على تفاصيل المادة.
تحتاج Web API method إلى بيانات بتنسيق JSON ، لذلك يجب عمل serializing للبيانات لتحويلها الى JSON ومن ثم تحويلها إلى نوع StringContent class.
الكود 
StringContent content = new StringContent(JsonConvert.SerializeObject(coursesModel), Encoding.UTF8, "application/json");

الان اصبح عندنا Object  من نوع StringContent باسم content يحتوي تفاصيل المادة الجديدة، الخطوة القادمة ارسال هذا Object الى Web API باستخدام httpClient.PostAsync على شكل   parameter ويكون رقم 2 
الكود 
using (var response = await httpClient.PostAsync("https://localhost:7081/api/Course/AddCourses", content))

تمام الان دور نشغل التطبيق ونشوف النتائج:
ادخل الرابط التالي في المتصفح: 
النتيجة بتكون:
تمام جرب دخل بيانات للمادة بعدها اضغط على add 
للتذكير : لازم مشروع API يكون شغال.
بعمل Breakpoint في مشروع Web API بتكون النتيجة مثل الصورة :
مثل ما هو واضح القيم موجوده في  CoursesModel 
الكود الأخير: 
           return Redirect("Index");
بنرجع الصفحة لصفحة عرض جميع المواد 

تمام الان بنطلب منك تطبيق Edit , و Delete حتي نتأكد ان الموضوع واضح ومفهوم. (إذا ما عرفت كيف راسلنا وبنبعثلك الطريقة)