إنشاء المشروع الأول في Web APIs in ASP.NET Core

-

الاول في Web APIs in ASP.NET Core 

لنبدأ بانشاء مشروع جديد في Visual Studio 2022 ، (رابط التحميل

 قم بشتغيل Visual Studio 2022  ثم اختر create new project 

واستخدم قالب ASP.Net core Web API لإنشاء مشروع ثم اضغط فوق Next 





في الشاشة التالية سنعمل على تسمية المشروع MyFirstAPIProject. تذكر تحديد إطار العمل كـ .NET Core والإصدار كـ ASP.NET Core 6.0. لقد أظهرت هذا في الصورة أدناه.

ثم انقر فوق Create وانتظر لبضع ثواني حتى بتم انشاء المشروع 

شكل المشروع :


تركيب المشروع:
 
كما تلاحظ ان المشروع الذي أنشأناه يحتوي على مجلد باسم Controllers، ويحتوي على API باسم WeatherForecast API. يمكن إزالة هذا.
إضافة NuGet packages الضرورية للعمل 
سنحتاج في هذا المشروع الى مجموعه من المكتبات الضرورية للعمل وهي: 


اسم packages
الوصف 
Microsoft.VisualStudio.Web.CodeGeneration.Design Version(6.0.2)
تساعد في انشاء controllers و Views
Microsoft.EntityFrameworkCore.Tools –Version(6.0.2)
تساعد هذه الحزمة في إنشاء  database contextو model classes  من قاعدة البيانات.
Microsoft.EntityFrameworkCore.SqlServer –Version(6.0.2)
يستخدم Entity Framework Core للعمل مع SQL Server.
System.IdentityModel.Tokens.Jwt  –Version(6.16.0)
يستخدم  لإنشاء رمز JWT والتحقق منه وذلك بهدف حماية API . سنتحدث بالتفصيل حول ذلك في الدروس اللاحقة
Microsoft.AspNetCore.Authentication.JwtBearer–Version(6.0.2)
هذا هو البرنامج الوسيط  middleware  الذي يمكّن تطبيق ASP.NET Core من تلقي bearer token في request pipeline.
بعد الإضافة سيكون شكل المشروع كالتالي :


قاعدة البيانات 

في دروس ASP.Net Core قمنا بإنشاء قاعدة بيانات خاصة بالطلاب والمواد سنستخدم نفس قاعدة البيانات لتحميل Script الخاص بقاعدة البيانات من هنا 

العمل مع connection string

كما تعلمنا في ASP.Net Core تحتوي مشاريع ASP.Net Core على ملف باسم appsettings.json و appsettings.Development.json يستخدم هذا الملف لإضافة connection string 
 

الفرق بين الملفات:

في appsettings.json تستخدم عند عمل release  للمشروع ونشره بشكل رسمي
appsettings.Development.json تستخدم اثناء التطوير والاختبار.(يستخدم عادتا مع قاعدة البيانات الخاصة بالاختبار) 
للمزيد حول ذلك شاهد الدرس ASP.Net connection string

إضافة connection string الخاصة بنا في هذه الملفات 
قم بتعديل الكود الموجود في هذا الملف الى 

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": 
    "STDCS": "Server=yourservername;Database=yourdatabase;User Id=userId; Password=Password;"
}

لاحظ ان اسم ConnectionStrings هو STDCS وتحتوي على اسم Server و اسم قاعدة البيانات ، اسم المستخدم ، كلمة المرو
تأكد من تغير هذه الخصائص بناء على قاعدة البيانات الخاصة بك

إضافة  DbContext (entity classes) الى المشروع 

يجب إضافة class ليتم تعريف واعداد الاتصال بقاعدة البيانات من خلاله لذا اضف class باسم STDDbContext ثم اضف الكود التالي الى هذا class:
 
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;
namespace MyFirstAPIProject
{
    public class STDDbContext : DbContext
    {
        private readonly IConfiguration _configuration;
        private IDbConnection DbConnection { get; }
        public STDDbContext(DbContextOptions<STDDbContext> options, IConfiguration configuration)
            : base(options)
        {
            this._configuration = configuration;
            DbConnection = new SqlConnection(this._configuration.GetConnectionString("STDCS"));
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer(DbConnection.ToString());
            }
        }
    }
}


تسجيل connecting string في ملف Program.cs 

في Net 6.  طريقة الإضافة مختلفة عن Net 5. حيث تم الاستغناء عن StartUp  في Net 6. 
 
لعمل ذلك أضف الكود التالي داخل ملف Program.cs

string connString = builder.Configuration.GetConnectionString("STDCS");
builder.Services.AddDbContext<STDDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});

بهذا نكون قد اتممنا اعداد المشروع وتجهيز قاعدة البيانات و entity classes 

الان سننتقل الى إضافة API 

إضافة CoursesModel 
سنعمل الان على إضافة class يحتوي على مكونات(الحقول) الخاصة بالمواد الدراسية 
لذا سنبدأ بإنشاء مجلد باسم Models ومن ثم اضف class باسم CoursesModel ثم اضف الكود التالي الى هذا class 

using System.ComponentModel.DataAnnotations;
namespace MyFirstAPIProject.Models
{
    public class CoursesModel
    {
        [Key]
        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; }
    }
}
لاحظ اننا اضفنا الكود [Key] قبل CoursesId وذلك بهدف تحديد هذا الحقل ك primary key 


إضافة Controller

انتقل الى المجلد Controller ثم اختر Add New Controller

ثم اختر الخيار كما في الصورة (سنستخدم DbContext التي أنشأناها سابقا). ثم اختر Add


من الشاشة التالية سنحدد CoursesModel في Model Class ثم اختر STDDbContext كما في الصورة، ثم تأكد من اسم Controller ليكون CourseController  ثم انقر فوق Add


  • انتظر لبضع ثواني وسيتم بشكل تلقائي انشاء API باستخدام تقنية ASP.NET CORE scaffolding مع الكود الخاص بهذا API
    تم انشاء مجموعة من  API هي: 
    •  GetCoursesModel() يستخدم HTTP Get  لعرض جميع المواد
    •  GetCoursesModel(int id) يستخدم HTTP Get  لعرض كورس بناء على رقم محدد
    • PutCoursesModel(int id, CoursesModel coursesModel) يستخدم طريقة HTTP Put
    • PostCoursesModel(CoursesModel coursesModel)  يستخدم HTTP Post لإنشاء كورس
    • DeleteCoursesModel(int id) يستخدم HTTP Delete  لحذف كورس يستخدم 
     
    وفقًا REST best practice، يتم تعيين كل endpoint ب HTTP methods بناءً على طريقة عملها.
    الكود التالي هو الكود الخاص ب controller : 
    nullable disable
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using MyFirstAPIProject;
    using MyFirstAPIProject.Models;
    namespace MyFirstAPIProject.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class CourseController : ControllerBase
        {
            private readonly STDDbContext _context;
            public CourseController(STDDbContext context)
            {
                _context = context;
            }
            // GET: api/Course
            [HttpGet]
            public async Task<ActionResult<IEnumerable<CoursesModel>>> GetCoursesModel()
            {
                return await _context.CoursesModel.ToListAsync();
            }
            // GET: api/Course/5
            [HttpGet("{id}")]
            public async Task<ActionResult<CoursesModel>> GetCoursesModel(int id)
            {
                var coursesModel = await _context.CoursesModel.FindAsync(id);
                if (coursesModel == null)
                {
                    return NotFound();
                }
                return coursesModel;
            }
            // PUT: api/Course/5
            // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
            [HttpPut("{id}")]
            public async Task<IActionResult> PutCoursesModel(int id, CoursesModel coursesModel)
            {
                if (id != coursesModel.CoursesId)
                {
                    return BadRequest();
                }
                _context.Entry(coursesModel).State = EntityState.Modified;
                try
                {
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!CoursesModelExists(id))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return NoContent();
            }
            // POST: api/Course
            // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
            [HttpPost]
            public async Task<ActionResult<CoursesModel>> PostCoursesModel(CoursesModel coursesModel)
            {
                _context.CoursesModel.Add(coursesModel);
                await _context.SaveChangesAsync();
                return CreatedAtAction("GetCoursesModel", new { id = coursesModel.CoursesId }, coursesModel);
            }
            // DELETE: api/Course/5
            [HttpDelete("{id}")]
            public async Task<IActionResult> DeleteCoursesModel(int id)
            {
                var coursesModel = await _context.CoursesModel.FindAsync(id);
                if (coursesModel == null)
                {
                    return NotFound();
                }
                _context.CoursesModel.Remove(coursesModel);
                await _context.SaveChangesAsync();
                return NoContent();
            }
            private bool CoursesModelExists(int id)
            {
                return _context.CoursesModel.Any(e => e.CoursesId == id);
            }
        }
    }

    لنقم بتشغيل التطبيق سيكون الشكل كما في الصورة:



    لاحظ ان اسم API هو نفس اسم Controller حيث يتم انشاء ذلك بشكل تلقائي. يمكن تغيير الاسم عن طريق تغيير Route .
    لتغير API الأول أضف الكود التالي فوق Function  
    [HttpGet]
    [Route("GetAllCourses")]
    public async Task<ActionResult<IEnumerable<CoursesModel>>> GetCoursesModel()
            {
                return await _context.CoursesModel.ToListAsync();
            }

    لنقم بإعادة تنفيذ المشروع ستكون النتيجة :


    سنقوم الان بإجراء بعض التعديلات على API  بهدف عرض جميع المواد الموجودة في قاعدة البيانات(سنتعامل مع قواعد البيانات باستخدام ADO.Net ) لذا سنقوم بكتابة بعض الكود لغرض جلب البيانات.
    اضف الكود التالي الى API :
    #nullable disable
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Data.SqlClient;
    using Microsoft.EntityFrameworkCore;
    using MyFirstAPIProject;
    using MyFirstAPIProject.Models;
    namespace MyFirstAPIProject.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class CourseController : ControllerBase
        {
           private readonly STDDbContext _context; 
            private readonly IHttpContextAccessor httpContextAccessor;
            private readonly IConfiguration configuration;
            CoursesModel CusModel = new CoursesModel();
            DataTable Dt = new DataTable();
            DbConnection connection;
          
            public CourseController(IConfiguration config, IHttpContextAccessor _httpContextAccessor)
            {
                configuration = config;
                httpContextAccessor = _httpContextAccessor;
            }

            // GET: api/Course
            [HttpGet]
            [Route("GetAllCourses")]
            public async Task<ActionResult<IEnumerable<CoursesModel>>> GetCoursesModel()
            {
                
                connection = new SqlConnection(configuration.GetConnectionString("STDCS"));
                List<CoursesModel> CoursesModelList = new List<CoursesModel>();
                try
                {
                   string sqlQuery ="select CoursesId, CourseNumber, CourseName, CourseDescription, Price, Capacity, CreatedDate  from courses";
                    DbProviderFactory dbFactory = DbProviderFactories.GetFactory(connection);
                    using (var cmd = dbFactory.CreateCommand())
                    {
                        cmd.Connection = connection;
                        cmd.CommandType = CommandType.Text;
                        cmd.CommandText = sqlQuery;
                        using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
                        {
                            adapter.SelectCommand = cmd;
                            await Task.Run(() => adapter.Fill(Dt));
                        }
                    }
                    foreach (DataRow BDM in Dt.Rows)
                    {
                        CoursesModelList.Add(new CoursesModel
                        {
                            CoursesId = Convert.ToInt16(BDM["CoursesId"]),
                            CourseNumber = BDM["CourseNumber"].ToString(),
                            CourseName = BDM["CourseName"].ToString(),
                            CourseDescription = Convert.ToString(BDM["CourseDescription"]),
                            Price = Convert.ToDecimal(BDM["Price"]),
                            Capacity = Convert.ToInt32(BDM["Capacity"]),
                        });
                    }
                }
                catch (Exception ex)
                {
                }
                finally
                {
    }
                return (CoursesModelList);
                //return await _context.CoursesModel.ToListAsync();
            }
            // GET: api/Course/5
            [HttpGet("{id}")]
            public async Task<ActionResult<CoursesModel>> GetCoursesModel(int id)
            {
                var coursesModel = await _context.CoursesModel.FindAsync(id);
                if (coursesModel == null)
                {
                    return NotFound();
                }
                return coursesModel;
            }
            // PUT: api/Course/5
            // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
            [HttpPut("{id}")]
            public async Task<IActionResult> PutCoursesModel(int id, CoursesModel coursesModel)
            {
                if (id != coursesModel.CoursesId)
                {
                    return BadRequest();
                }
                _context.Entry(coursesModel).State = EntityState.Modified;

                try
                {
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!CoursesModelExists(id))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return NoContent();
            }
            // POST: api/Course
            // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
            [HttpPost]
            public async Task<ActionResult<CoursesModel>> PostCoursesModel(CoursesModel coursesModel)
            {
                _context.CoursesModel.Add(coursesModel);
                await _context.SaveChangesAsync();
                return CreatedAtAction("GetCoursesModel", new { id = coursesModel.CoursesId }, coursesModel);
            }

            // DELETE: api/Course/5
            [HttpDelete("{id}")]
            public async Task<IActionResult> DeleteCoursesModel(int id)
            {
                var coursesModel = await _context.CoursesModel.FindAsync(id);
                if (coursesModel == null)
                {
                    return NotFound();
                }
                _context.CoursesModel.Remove(coursesModel);
                await _context.SaveChangesAsync();
                return NoContent();
            }
            private bool CoursesModelExists(int id)
            {
                return _context.CoursesModel.Any(e => e.CoursesId == id);
            }
        }
    }
    لنقم بتنفيذ المشروع بالنقر فوق F5 ثم انقر قوق  
    api/Course/GetAllCourses

     ثم اختر  Try  it Out  كما في الصورة


    ثم انقر فوق Execute  كما في الصورة:


    ستكون نتيجة التنفيذ أسفل الشاشة كما في الصورة: 


    يمكن تنفيذ الAPI عن طريق الرابط :
    https://localhost:7081/api/Course/GetAllCourses

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


    سنقوم بإنشاء API يعمل على ارجاع البيانات الموجودة في جدول courses في قاعدة البيانات StudentsAcademy
    شرح الكود السابق :

    فيما يلي شرح بالتفصيل للاكواد التي تم اضافتها سابق 
    سنبدأ بالكود  constructor
    private readonly STDDbContext _context; 
    private readonly IConfiguration configuration;
    CoursesModel CusModel = new CoursesModel();
    DataTable Dt = new DataTable();
    DbConnection connection;
    public CourseController(IConfiguration config)
            {
                configuration = config;
         
            }
    قمنا بتعريف constructor خاص بهذا controller حيث تم تمرير parameter التالي:
    IConfiguration config : بهدف الوصول الى concoction string  الموجودة في ملف appsettings.json
    وقمنا بإضافة متغير من نوع IConfiguration لاستخدامه مع هذه constructor

    الكود الخاص ب GetAllCourses

    // GET: api/Course
            [HttpGet]
            [Route("GetAllCourses")]
            public async Task<ActionResult<IEnumerable<CoursesModel>>> GetCoursesModel()
            {
                
                connection = new SqlConnection(configuration.GetConnectionString("STDCS"));
                List<CoursesModel> CoursesModelList = new List<CoursesModel>();
                try
                {
                   string sqlQuery ="select CoursesId, CourseNumber, CourseName, CourseDescription, Price, Capacity, CreatedDate  from courses";
                    DbProviderFactory dbFactory = DbProviderFactories.GetFactory(connection);
                    using (var cmd = dbFactory.CreateCommand())
                    {
                        cmd.Connection = connection;
                        cmd.CommandType = CommandType.Text;
                        cmd.CommandText = sqlQuery;
                        using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
                        {
                            adapter.SelectCommand = cmd;
                            await Task.Run(() => adapter.Fill(Dt));
                        }
                    }
                    foreach (DataRow BDM in Dt.Rows)
                    {
                        CoursesModelList.Add(new CoursesModel
                        {
                            CoursesId = Convert.ToInt16(BDM["CoursesId"]),
                            CourseNumber = BDM["CourseNumber"].ToString(),
                            CourseName = BDM["CourseName"].ToString(),
                            CourseDescription = Convert.ToString(BDM["CourseDescription"]),
                            Price = Convert.ToDecimal(BDM["Price"]),
                            Capacity = Convert.ToInt32(BDM["Capacity"]),
                        });
                    }
                }
                catch (Exception ex)
                {
                }
                finally
                {
                 }
                return (CoursesModelList);
                //return await _context.CoursesModel.ToListAsync();
            }

    هنا قمنا بإضافة كود باستخدام ADO.Net لإرجاع البيانات الموجودة في جدول courses وتم تخزين النتيجة في DataTable  باسم Dt  وتم بعد ذلك ارجاع البيانات في List من نوع CoursesModel وذلك لان نوع API هو :IEnumerable<CoursesModel>

    لننتقل الان الى GetAllCourses باستخدام Parameter 

    أولا سنعمل على تغيير اسم هذا API الى [Route("GetCoursesById")]حيث يعمل هذا API على ارجاع قيمة محددة بناء على رقمها ، سنرسل رقم الكورس لهاذ API وستكون النتيجة بناء على هذا ID

    التغيير الوحيد هنا هو إضافة Parameter  بقيمة ID  الى string sqlQuery لذا اضف الكود التالي داخل هذا API (التغيير الوحيد هنا تم تميزيه باللون الأصفر)


    // GET: api/Course/5
            [HttpGet]
            [Route("GetCoursesById")]
            public async Task<ActionResult<IEnumerable<CoursesModel>>> GetCoursesModel(int id)
            {
                connection = new SqlConnection(configuration.GetConnectionString("STDCS"));
                List<CoursesModel> CoursesModelList = new List<CoursesModel>();
                try
                {
                    string sqlQuery = "select CoursesId, CourseNumber, CourseName, CourseDescription, Price, Capacity, CreatedDate  from courses where CoursesId="+id;
                    DbProviderFactory dbFactory = DbProviderFactories.GetFactory(connection);
                    using (var cmd = dbFactory.CreateCommand())
                    {
                        cmd.Connection = connection;
                        cmd.CommandType = CommandType.Text;
                        cmd.CommandText = sqlQuery;
                        using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
                        {
                            adapter.SelectCommand = cmd;
                            await Task.Run(() => adapter.Fill(Dt));
                        }
                    }

                    foreach (DataRow BDM in Dt.Rows)
                    {
                        CoursesModelList.Add(new CoursesModel
                        {
                            CoursesId = Convert.ToInt16(BDM["CoursesId"]),
                            CourseNumber = BDM["CourseNumber"].ToString(),
                            CourseName = BDM["CourseName"].ToString(),
                            CourseDescription = Convert.ToString(BDM["CourseDescription"]),
                            Price = Convert.ToDecimal(BDM["Price"]),
                            Capacity = Convert.ToInt32(BDM["Capacity"]),
                        });
                    }
                }
                catch (Exception ex)
                {
                }
                finally
                {
                }
                return (CoursesModelList);
            }
    لنقم بتشغيل التطبيق بنفس الطريقة السابقة 
    لاحظ انه عند التنفيذ تم إضافة textbox لإرسال رقم CourseId كما في الصورة :

    او من خلال الرابط 
    https://localhost:7081/api/Course/GetCoursesById?id=1

    ستكون نتيجة التنفيذ 
    فيما يتعلق ب Delete و Update سنطلب منك محاولة تطبيق ذلك بشكل شخصي حتي نتأكد من فهم الموضوع بشكل جيد 
    اذا لم تتمكن من ذلك راسلنا وسنقوم بالتواصل معك لشرح الطريقة.