ارتباط اپلیکیشن Xamarin Forms و ASP.NET Core Web API

پرووید

دسته های مقالات

با یکی دیگر از آموزش های رایگان وبسایت پرووید در حوزه ی ASP.NET Core و Xamarin Forms در خدمت شما هستیم. در این آموزش قصد داریم نحوه ی برقراری ارتباط بین یک برنامه ی Xamarin Forms و backend service های ASP.NET Core را با شما بررسی کنیم. در همین ابتدا توصیه می کنیم که به منظور یادگیری هر چه بهتر مفاهیم مطرح شده در این آموزش از آموزش پروژه محور و پیشرفته ی اپلیکیشن های موبایل با Xamarin.Forms استفاده کنید. ضمناً، می توانید از آموزش پیاده سازی Web API در ASP.NET Core 2.0 نیز به منظور یادگیری مفاهیم مربوط به Web API و backend service ها استفاده کنید.

ساخت یک native mobile app

همانطور که ممکن است بدانید mobile application هایی که در xamarin forms ساخته می شوند بر روی platform های مختلف از قبیل اندروید و iOS و UWP به صورت native اجرا می شوند. این موضوع در بسته ی آموزش ویدئویی عمیق Xamarin Forms و ساخت برنامه های Cross-Platform مطرح شده است. در این آموزش ما قصد داریم یک client application با نام ToDORest را که با استفاده از Xamarin Forms نوشته شده است را استفاده کنیم. به منظور انجام این کار، می توانید این mobile application را دانلود کرده و کار را آغاز کنید.

todo-android

در درون پروژه ی دانلود شده خواهید دید که یک پروژه ی ASP.NET Web API 2 نیر قرار دارد که ما در این آموزش آن را با یک برنامه ASP.NET Core جایگزین خواهیم کرد. دقت کنید که این تغییر هیچ تاثیری بر روی روند کار client application نخواهد داشت. در تصویر زیر نمایی ساده را از برنامه ی Todo را می بینید.

قابلیت های برنامه ی Todo

در زبان انگلیسی کلمه ی To Do به معنی کارهایی است که باید انجام دهیم. در واقع این برنامه نیز به منظور مدیریت همین کارهای روزمره نوشته شده است.

برنامه ی مذکور قابلیت لیست کردن، اضافه کردن، حذف کردن و به روز رسانی تعدادی To-Do item را دارد. هر کدام از این آیتم ها از property هایی با نام های ID، Name و Notes تشکیل شده است. همچنین یک property با نام Done که مشخص کننده ی این موضوع است که آیا یک To-Do item انجام شده است یا نه. همانطور که در تصویر بالا مشاهده کردید view ابتدایی برنامه یک لیست از ایتم ها را نشان می دهد و همچنین مشخص می کند که آیا یک ایتم انجام شده است یا نه. با استفاده از دکمه ی + که در قسمت بالایی صفحه مشاهده می کنید می توانید یک To-Do item جدید اضافه کنید. این موضوع در تصویر زیر نشان داده شده است.

todo-android-new-item

این کار به آسانی به وسیله ی navigation در Xamarin Forms قابل انجام است. در رابطه با navigation در بسته ی آموزش ویدئویی عمیق Xamarin Forms و ساخت برنامه های Cross-Platform صحبت کرده ایم.

علاوه بر قابلیت اضافه کردن یک ایتم جدید، اگر بر روی یکی از ایتم ها که در view ابتدایی برنامه در یک لیست نشان داده شده اند tap (یعنی با انگشت فشار دهید.) کنید به view زیر navigate می کنید.

todo-android-edit-item

با استفاده از این view می توانید property های Name، Notes و Done را تغییر دهید و یا یک ایتم را حذف کنید.

این mobile application طوری پیکربندی شده است که از backend service های host شده در developer.xamarin.com استفاده کند. این backend service ها امکان انجام تعدادی operation های read-only را می دهند. به منظور تست کردن این mobile application با برنامه ی ASP.NET Core ای که در قسمت بعد خواهید ساخت باید یک constant با نام RestUrl را اپدیت کنید. برای انجام این کار به پروژه ی ToDoREST رفته و فایل Constants.cs را باز کنید و RestUrl را با IP سیستم خودتان تنظیم کنید. دقت کنید که IP تنظیم شده نباید 127.0.0.1 و یا حتی localhost باشد چرا که این آدرس از روی device emulator استفاده می شود از روی ویندوز. ضمناً می توانید port number را هم با مقدار 5000 تنظیم کنید. برا این که تست کنید که برنامه بتواند با اجرا شدن در emulator با backend service ها کار کند، firewall را غیرفعال کنید. کد زیر نحوه ی تغییر دادن RestUrl را نشان می دهد.

 
public static string RestUrl = "http://192.168.1.207:5000/api/todoitems/{0}";

ساخت پروژه ی ASP.NET Core

به منظور ساخت پروژه ی ASP.NET ای که قرار است در این آموزش از آن استفاده کنیم، در ویژوال استادیو با یک ASP.NET Core Web Application بسازید و در پنچره ی دوم شبیه تصویر زیر Web API را و No Authentication را انتخاب کنید. نام پروژه را ToDoApi بگذارید.

web-api-template

از شما دعوت می کنیم که از آموزش پیاده سازی Web API در ASP.NET Core 2.0 و همچنین بسته ی آموزش ویدئویی شروع به کار برنامه نویسی ASP.NET Core که پیشتر بر روی وبسایت پرووید قرار گرفته اند استفاده کنید. برنامه ی ما باید بتواند به تمامی request هایی که به پورت 5000 وارد می شوند پاسخ بدهد، بنابراین در فایل Program.cs دستور UseUrls را شبیه به کد زیر وارد کنید.

 
var host = new WebHostBuilder()
    .UseKestrel()
    .UseUrls("http://*:5000")
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseIISIntegration()
    .UseStartup()
    .Build();

نکته ی بسیار مهمی که نباید فراموش کرد این است که در زمان اجرا کردن برنامه باید آن را به صورت مستقیم اجرا کنید و نه از طریق IIS Express، چرا که در این صورت request های local در نظر گرفته نخواهند شد. برای اینکه برنامه را به صورت مستقیم اجرا کنید می توانید از دستور detnet run در command prompt استفاده کنید. علاوه بر این، می توانید از قسمت Debug Target در toolbar ویژوال استادیو نام برنامه را انتخاب کنید.

اضافه کردن model برنامه

کلاسی که به عنوان model برنامه عمل می کند ToDoItem نام دارد. کد مربوط به این کلاس در قسمت زیر نشان داده شده است.

 
using System.ComponentModel.DataAnnotations;

namespace ToDoApi.Models
{
    public class ToDoItem
    {
        [Required]
        public string ID { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public string Notes { get; set; }

        public bool Done { get; set; }
    }
}

لطفا به attribute های [Required] که بر روی property ها لحاظ شده اند دقت کنید. از آنجایی که متدهای Web API باید بتوانند به طریقی با داده ها کار کنند ازیک interface با نام IToDoRepository که در برنامه ی Xamarin اصلی قرار داده شده است استفاده می کنیم. کد مربوط به این interface در قسمت پایین آمده است.

 
using System.Collections.Generic;
using ToDoApi.Models;

namespace ToDoApi.Interfaces
{
    public interface IToDoRepository
    {
        bool DoesItemExist(string id);
        IEnumerable All { get; }
        ToDoItem Find(string id);
        void Insert(ToDoItem item);
        void Update(ToDoItem item);
        void Delete(string id);
    }
}

به منظور پیاده سازی این interface کلاسی با نام ToDoRepository را تعریف می کنیم. کد مربوط به این کلاس در قسمت زیر مشاهده می شود.

 
using System.Collections.Generic;
using System.Linq;
using ToDoApi.Interfaces;
using ToDoApi.Models;

namespace ToDoApi.Services
{
    public class ToDoRepository : IToDoRepository
    {
        private List _toDoList;

        public ToDoRepository()
        {
            InitializeData();
        }

        public IEnumerable All
        {
            get { return _toDoList; }
        }

        public bool DoesItemExist(string id)
        {
            return _toDoList.Any(item => item.ID == id);
        }

        public ToDoItem Find(string id)
        {
            return _toDoList.FirstOrDefault(item => item.ID == id);
        }

        public void Insert(ToDoItem item)
        {
            _toDoList.Add(item);
        }

        public void Update(ToDoItem item)
        {
            var todoItem = this.Find(item.ID);
            var index = _toDoList.IndexOf(todoItem);
            _toDoList.RemoveAt(index);
            _toDoList.Insert(index, item);
        }

        public void Delete(string id)
        {
            _toDoList.Remove(this.Find(id));
        }

        private void InitializeData()
        {
            _toDoList = new List();

            var todoItem1 = new ToDoItem
            {
                ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
                Name = "Learn app development",
                Notes = "Attend Xamarin University",
                Done = true
            };

            var todoItem2 = new ToDoItem
            {
                ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
                Name = "Develop apps",
                Notes = "Use Xamarin Studio/Visual Studio",
                Done = false
            };

            var todoItem3 = new ToDoItem
            {
                ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
                Name = "Publish apps",
                Notes = "All app stores",
                Done = false,
            };

            _toDoList.Add(todoItem1);
            _toDoList.Add(todoItem2);
            _toDoList.Add(todoItem3);
        }
    }
}

در در حال حاضر data store برنامه یک collection از ToDoItem ها می باشد که به صورت private در این Repository تعریف شده است. در ادامه نیاز به انجام پیکربندی dependency های برنامه در فایل Startup.cs داریم.

 
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.AddSingleton<IToDoRepository,ToDoRepository>();
}

همانطور که در کد بالا مشاهده می کنید IToDoRepository که یک dependency است به ToDoRepository که یک کلاسی است که این اینترفیس را پیاده سازی کرده است resolve می شود. در مورد dependency injection و inversion of control در آموزش معکوس سازی کنترل Inversion of Control در سی شارپ از وبسایت پرووید صحبت کرده ایم.

ساختن Controller

به منظور ساختن controller برنامه یک فایل با نام ToDoItemsController را اضافه کنید. شاید بدانید که controller ها باید از کلاس Microsoft.AspNetCore.Mvc.Controller ارث بری کنند. از attribute ای با نام [Route] به منظور پیکربندی route ها استفاده می کنیم. در واقع می خواهیم برنامه بتوانید تمامی request هایی که به آدرسی با پیشوند api/todoitems وارد می شوند را هندل کند. کد زیر را مشاهده کنید.

 
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ToDoApi.Interfaces;
using ToDoApi.Models;

namespace ToDoApi.Controllers
{
    [Route("api/[controller]")]
    public class ToDoItemsController : Controller
    {
        private readonly IToDoRepository _toDoRepository;

        public ToDoItemsController(IToDoRepository toDoRepository)
        {
            _toDoRepository = toDoRepository;
        }
    }
}	

در این کد و در [Route] یک token با نام [controller] قرار دارد که با نام controller مورد نظر جایگزین خواهد شد و البته پسوند Controller که باید در پایان نام هر controller ای قرار بگیرد نیز حذف می شود.

خب همانطور که در کد بالا مشاهده می کنید controller مورد نظر یک وابستگی به IToDoRepository دارد که به عنوان پارامتر ورودی تابع سازنده و در قالب یک dependency آن را دریافت می کند. این موضوع به صورت خودکار توسط IoC container ای که در ASP.NET Core قرار گرفته است انجام می شود.

این API از چهار عمل اصلی CRUD پشتیبانی می کند و می تواند با انواع مختلفی از HTTP verb ها کار کند. ساده ترین کاری که این API انجام می دهد خواندن داده ها با استفاده از HTTP GET می باشد.

خواندن ایتم ها

به منظور خواندن داده ها از متد List که در controller تعریف خواهیم کرد استفاده می کنیم. بدون شک باید [HttpGet] را به عنوان attribute این متد لحاظ کنیم تا به Web API بفهمانیم که این متد مسئول هندل کردن Get request ها می باشد. ضمناً route مربوط به action method در درون controller تنظیم شده است. می دانید که لزوماً نیازی به ذکر کردن action name در route ندارید. تنها کاری که باید از انجام شدن اطمینان حاصل کنید این است که هر action باید یک route یکتا داشته باشد. ضمناً، attribute های مربوط به routing می توانند در سطح controller و method لحاظ شوند. کد زیر متد List را نشان می دهد.

 
[HttpGet]
public IActionResult List()
{
    return Ok(_toDoRepository.All);
}

در این متد یک response code به صورت 200 OK به همراه تمامی ToDoItem هایی که serialize شده اند برگردانده می شود. در رابطه با serialization توصیه می کنیم حتماً آموزش سریالیزیشن Serialization در سی شارپ را استفاده کنید. ضمناً، شبیه تصویر زیر از Postman به منظور تست کردن Web API ای که تا اینجا ساخته ایم استفاده می کنیم.

postman-get

ساختن ایتم ها

بر اساس convention ها (قوانین پیش فرضی که در یک فریم ورک وجود دارند. می توانید در مورد convention ها در Entity Framework Core نیز مطالعه ای کنید.)، ساختن یک data item جدید به یک HTTP Post نگاشت خواهد شد. متدی که در ادامه خواهید دید Create نام دارد و [HttpPost] به عنوان یک attribute بر روی آن لحاظ شده است. علاوه بر این، یک شی از کلاس ToDoItem به عنوان پارامتر ورودی دریافت می کند. نام این پارامتر ورودی item است و از آنجایی که در یک متد POST پارامتر ووردی در request body قرار می گیرد، این پارامتر با [FromBody] اصطلاحاً تزئین و یا همان decorate شده است. لطفاً کد مربوط به این متد را مشاهده کنید.

 
[HttpPost]
public IActionResult Create([FromBody] ToDoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        bool itemExists = _toDoRepository.DoesItemExist(item.ID);
        if (itemExists)
        {
            return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
        }
        _toDoRepository.Insert(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
    }
    return Ok(item);
}

در بدنه ی این متد در ابتدا valid بودن پارامتر ورودی و اینکه از قبل بر روی بانک اطلاعاتی وجود نداشته باشد چک می شود و سپس اگر مشکلی وجود نداشت با استفاده از Repository به دیتابیس اضافه می شود. شاید جالب باشد بدانید که Repository یک design pattern است که ابتدا در Domain Driven Design مطرح شد.

در متد Create با استفاده از ModelState.IsValid فرآیند model validation را انجام داده ایم که باید در هر API method ای که یک پارامتر ورودی را از کاربر دریافت می کند لحاظ شود.

موضوع دیگری که قصد داریم به آن اشاره کنیم یک enum است که با نام ErrorCode تعریف می شود و یک error code را به mobile client ارسال می کند. کد زیر این موضوع را نشان می دهد.

 
public enum ErrorCode
{
    TodoItemNameAndNotesRequired,
    TodoItemIDInUse,
    RecordNotFound,
    CouldNotCreateItem,
    CouldNotUpdateItem,
    CouldNotDeleteItem
}

در رابطه با enum ها در آموزش شی گرایی OOP در سی شارپ بیشتر مطالعه کنید. در ادامه با استفاده از Postman اقدام به تست کردن متد Create کنید. فقط فراموش نکنید که باید یک شی جدید از نوع ToDoItem را به صورت JSON در request body تعریف کنید و هم چنین در request header مورد Content-Type را به صورت application/json تنظیم کنید. این موضوع در تصویر زیر نشان داده شده است.

postman-post

دقت کنید که متد Create شی جدیدی که ساخته شده است را در response بر می گرداند.

اپدیت کردن ایتم ها

اپدیت کردن ToDoItem ها تا حد زیادی شبیه به ساختن آنها انجام می شود. به همین دلیل متد Edit که در ادامه آن را مشاهده می کنید تا حد زیادی شبیه به متد Create است. تفاوت اصلی این متد در این است که از attribute ای با نام [HttpPut] استفاده می کند. علاوه بر این، اگر این متد نتواند ToDoItem ای که قصد ویرایش کردن آن را دارید پیدا کند یک NotFound (404) response بر می گرداند. کد زیر متد Edit را نشان می دهد.

 
[HttpPut]
public IActionResult Edit([FromBody] ToDoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        var existingItem = _toDoRepository.Find(item.ID);
        if (existingItem == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _toDoRepository.Update(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
    }
    return NoContent();
}

به منظور تست کردن متد Edit از طریق Postman ابتدا HTTP verb را بر روی Put تنظیم کنید و سپس داده ی به روز شدهی object مورد نظر را در request body تنظیم کنید. این موضوع در تصویر زیر نشان داده شده است.

postman-put

به منظور ایجاد consistency با API ای که از قبل در این پروژه بوده است، متد Edit در صورت موفقیت آمیز بودن عملیاتش یک NoContent (204) response بر می گرداند.

دیلیت کردن ایتم ها

دیلیت کردن ایتم ها با ارسال یک DELETE request به سرویس انجام می شود. ضمناً، باید ID آن ایتمی که قصد حذف کردن آن را داریم نیز ارسال کنیم. اگر ایتمی که قصد حذف کردن آن را داریم موجود نباشد شبیه به قبل یک NotFound response دریافت خواهیم کرد و اگر عملیات حذف با موفقیت انجام شود یک NoContent (204) response دریافت خواهیم کرد. کد زیر مربوط به متد Delete می باشد.

 
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
    try
    {
        var item = _toDoRepository.Find(id);
        if (item == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _toDoRepository.Delete(id);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
    }
    return NoContent();
}

به منظور تست کردن متد Delete با Postman نیازی به ارسال کردن هیچ چیزی در request body نیست. این موضوع در تصویر زیر نشان داده شده است.

postman-delete

بررسی convention های معمول در Web API

با شروع به توسعه backend service ها، خواهید دید که اهمیت داشتن convention های consistent و سازگار چقدر زیاد است. این موضوع در هندل کردن cross-cutting concerns نیز بسیار مهم است. (cross-cutting concerns شامل مواردی می شوند که در سرتاسر برنامه باید لحاظ شوند: مثلاً authentication و logging و caching. در مورد cross-cutting concerns در آموزش معماری نرم افزارهای پیشرفته در دات نت صحبت کرده ایم.)

به عنوان convention در برنامه ای که در این آموزش ایجاد کردیم دیدید که زمانی که یک ایتم موجود نبود (چه برای Edit و چه برای Delete) یک NotFound response برگردانده میشد و نه یک BadRequest response. به علاوه، متدهایی که در controller یک پارامتر ورودی را دریافت می کردند همیشه ModelState.IsValid را بررسی می کردند و در صورت valid نبودن یک BadRequest response را بر میگرداندند. به منظور encapsulate کردن convention ها و policy های مختلف می توانید از filter ها در ASP.NET Web API استفاده کنید.

خب این آموزش از وبسایت پرووید را نیز در این قسمت به پایان می رسانیم. امیدواریم که این آموزش نیز مورد توجه تمامی دوستان عزیز قرار گرفته باشد. از شما دعوت می کنیم که حتماً از آموزش پروژه محور و پیشرفته ی اپلیکیشن های موبایل با Xamarin.Forms استفاده کنید. در این بسته ی آموزشی از مفاهیمی از قبیل dependency injection و backend service های Web API استفاده کرده ایم.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *