สร้าง Web API สำหรับส่ง iOS Push Notification ด้วย dotAPNS

ความสามารถของแอปพลิเคชันในปัจจุบันที่จะขาดไม่ได้เลย อย่าง Push Notification นั้น สำหรับนักพัฒนาชาว .Net แน่นอนครับว่ามี Library มากมายให้เลือกใช้ ในบทความนี้ผมจะเล่าวิธีการหนึ่ง ที่ได้เลือกใช้เพื่อตอบโจทย์การเชื่อมต่อแอปพลิเคชันกับหลายๆฐานข้อมูล การที่เราจะไป Deploy ตัวส่ง (Sender) ไว้ทุกๆ Server นั้นในด้าน Compatibility, Configuration, Maintain นั้นมีปัญหาแน่นอน เพราะข้อกำหนดของ Apple นั้นมีหลายอย่างพอสมควร ก่อนที่ตัวส่งของเรา จะได้รับอนุญาตุให้ส่งข้อความไปหาผู้ใช้ได้ ดังนั้นการติดตั้ง Sender ในแบบ Web API ไว้เพียงที่เดียว แล้วให้ทุก Client ที่ต้องการส่งมาเรียกใช้งาน จึงเป็นการลดปัญหาข้างต้นได้

สิ่งที่จะต้องมี

1.KeyID, BundleID, TeamID ทั้งสามค่านี้สามารถดูได้จาก appstoreconnect ในรายละเอียดของแอปพลิเคชัน

2.Push Notification Certification (.p8) ในบทความนี้จะใช้วิธีการส่งแบบ Token Based ซึ่งมีความยืดหยุ่นและบำรุงรักษาได้ง่ายกว่า แบบ Certification Based แต่อย่างไรก็ตามยังคงต้องมี Certificate สำหรับการยืนยันตัวตนประกอบการสร้าง Token อยู่ดี ซึ่งสามารถเข้าไปสร้างได้ที่ developer.apple.com > Account > Key เลือกสร้างแบบ
Apple Push Notifications service (APNs)

3. Web Server ที่สามารถเชื่อมต่อด้วย HTTPS, HTTP/2 ได้

สร้างโปรเจค Web API

ในบทความนี้ใช้ Visual Studio 2019 Version 16.8.0

1.ไปที่ File > New > Project เลือกเทมเพลต ASP.NET Web Application (.Net Framework) กดปุ่ม Create

2.เลือกรูปแบบโปรเจคเป็นแบบ Web API กดปุ่ม Create

3.เมื่อสร้างโปรเจคสำเร็จจะได้ไฟล์ต่างๆดังรูป (Main.aspx, MainController.cs สร้างเองภายหลัง สามารถตั้งชื่ออื่นๆได้)

4.ทำการเพิ่ม Library dotAPNS ด้วย Nuget Manager โดยการคลิกขวาที่โปรเจค เลือกเมนู Manage Nuget Packages ค้นหาด้วยคำว่า dotAPNS จากนั้นกดปุ่ม Install

5. ในไฟล์ MainController.cs ทำการ using dotAPNS มาใช้งาน จากนั้นสร้าง Method ชื่อ SendPush (ไม่จำเป็นต้องชื่อนี้) โดยโค้ดการสร้าง Token และส่ง Push Notification ทั้งหมดมีดังนี้

using System.Net.Http;
using System.Web.Http;
using System.Threading.Tasks;
using System.Configuration;
using dotAPNS;
using System.Web;
using System;

namespace APNsService.Controllers
{
    [RoutePrefix("api")]
    public class MainController : ApiController
    {
        [Route("SendPush")]
        [AcceptVerbs("GET", "POST")]
        public async Task<ApnsResponse> SendPush(string title, string body, string token, string category)
        {
            try
            {
                //อ่านค่า CertContent หรือ CertFilePart, BundleID, KeyID,TeamID มาจาก ไฟล์ web.config
                string certContent = ConfigurationManager.AppSettings["CertContent"];
                string certFilePath = ConfigurationManager.AppSettings["CertFilePath"];
                string bundleId = ConfigurationManager.AppSettings["BundleId"];
                string keyId = ConfigurationManager.AppSettings["KeyId"];
                string teamId = ConfigurationManager.AppSettings["TeamId"];

                //สร้าง Option สำหรับ Token
                var options = new ApnsJwtOptions()
                {
                    CertContent = certContent,
                    BundleId = bundleId,
                    KeyId = keyId,
                    TeamId = teamId
                };

                //สร้าง Token
                var apns = ApnsClient.CreateUsingJwt(new HttpClient(new WinHttpHandler()), options);

                //สร้าง Push Object
                var push = new ApplePush(ApplePushType.Alert)
                    .AddAlert(HttpUtility.UrlDecode(title), HttpUtility.UrlDecode(body))//ข้อความแจ้งเตือน
                    .AddBadge(0)//แสดงจำนวนแจ้งเตือนที่หน้าแอปพลิเคชัน
                    .AddCategory(category)//กำหนดกลุ่มของการแจ้งเตือน
                    .AddToken(token);//token
                
                //ส่ง Notification
                return await apns.Send(push);
            }
            catch (Exception ex)
            {
                return ApnsResponse.Error(ApnsResponseReason.Unknown, ex.Message + ex.StackTrace);
            }
        }
    }
}

จากโค้ดด้านบน สามารถทราบว่าส่วนไหนทำหน้าที่อะไรได้จาก Comment ครับ เพียงเท่านี้ก็สามารถ Publish และ Deploy Web API บน Server เพื่อทดสอบการใช้งานได้แล้ว อย่างไรก็แล้วแต่ Web API ดังกล่าวยังต้องการ การปรับแต่งให้เหมาะสมกับระบบของนักพัฒนาแต่ละท่าน แต่โดยขั้นตอนหลักๆก็มีเพียงเท่านี้ครับ

6.หน้าฟอร์ม และตัวอย่างโค้ดเรียกใช้งาน (อยู่ในหน้า Main.aspx,Main.aspx.cs เพื่อใช้ทดสอบการส่ง)

using System;
using System.IO;
using System.Configuration;
using System.Net;
using Newtonsoft.Json;

namespace APNsService
{
    public partial class Main : System.Web.UI.Page
    {
        string token = string.Empty;
        string baseUrl = string.Empty;

        protected void Page_Load(object sender, EventArgs e)
        {
            token = ConfigurationManager.AppSettings["apiToken"].ToString();
            baseUrl = string.Format("{0}://{1}{2}/", Request.Url.Scheme, Request.Url.Authority, Request.ApplicationPath.TrimEnd('/'));
        }

        protected void btnSend_Click(object sender, EventArgs e)
        {
            string url = string.Format("{0}api/SendPush?title={1}&body={2}&category={3}&token={4}", baseUrl, txtTitle.Text,txtBody.Text,txtCategory.Text,txtToken.Text);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            request.ContentType = "application/json";
            request.Headers.Add("token", token);

            try
            {
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                using (Stream responseStream = response.GetResponseStream())
                {
                    StreamReader reader = new StreamReader(responseStream, System.Text.Encoding.UTF8);
                    string resultJSON = reader.ReadToEnd();
                    var result = JsonConvert.DeserializeObject<APNsResult>(resultJSON);
                    txtResult.Text = resultJSON;
                    txtURL.Text = url;
                    btnSend.Focus();
                }
            }
            catch (Exception ex) 
            {
                txtResult.Text = ex.Message;
            }
        }

    }

    public class APNsResult
    {
        public bool IsSuccess { get; set; }
        public int Reason { get; set; }
        public string ReasonString { get; set; }
    }
}

สังเกตุว่าในส่วนของการส่งจาก Client อื่นๆผ่าน Web API นั้นต้องการเพียงแค่ HttpWebRequest, Newtonsoft.Json เท่านั้นทำให้มีขนาดเล็ก และลดปัญหาความเข้ากันได้ในการติดตั้ง Client ไปได้อย่างแน่นอนครับ

ขั้นตอนที่มีความสำคัญแต่ยังไม่ได้กล่าวถึงในบทความนี้นั้นคือการขอ Token ระบุเครื่องเป้าหมายจากผู้ใช้ที่ติดตั้งแอปพลิเคชันของเราครับ เนื่องจากวิธีการจะจำเพาะกับเครื่องมือที่ใช้ในการพัฒนาแอปพลิเคชัน