ในตอนนี้เราจะดึงความสามารถจาก Windows Azure ให้มากยิ่งขึ้น ด้วยการทำให้ Windows Azure Mobile Service สามารถส่ง Push Notification มายังแอพลิเคชันของเราได้เมื่อเอกสารถูก fork นอกจากนี้เราจะเอาแอพลิเคชันที่พัฒนาเสร็จเรียบร้อยแล้วส่งไปใน Windows 8 App Store อีกด้วย
เราจะเพิ่มเติมความสามารถของแอพลิเคชันจากบทความตอนที่แล้วให้สร้างเอกสารใหม่จากเอกสารเดิม หรือเรียกว่า fork ได้ โดยหากมีการ fork เกิดขึ้น ให้ส่ง Push Notification ไปแจ้งเจ้าของเอกสารที่ถูก fork ด้วย
ผู้อ่านสามารถดาวน์โหลดโปรเจกต์ก่อนเริ่มแก้ไขและโปรเจกต์หลังแก้ไขเสร็จเรียบร้อยแล้วได้ที่นี่
การตั้งค่าเพื่อใช้งาน Push Notification นั้น ก่อนอื่นเราจะต้องลงทะเบียนแอพลิเคชันของเราใน Windows App Store และเชื่อมโยงโปรเจกต์ใน Visual Studio เข้ากับแอพลิเคชันที่ลงทะเบียนไว้เสียก่อน (วิธีการทำอ่านได้จากส่วนหลังของบทความตอนนี้)
จากนั้นให้เปิดไฟล์ package.appxmanifest ที่แท็บ Application UI หัวข้อ Visual Assets ให้หาฟิลด์ Toast capable แล้วกำหนดค่าเป็น Yes เพื่อให้โปรเจกต์แสดง Toast Notification ได้
เมื่อเสร็จเรียบร้อยแล้วให้เข้าไปที่หน้าจัดการแอพลิเคชันของ Live Connect Developer Center จะปรากฎแอพลิเคชันที่เราลงทะเบียนไว้ใน Windows App Store ด้วย ให้เข้าไปในแอพลิเคชันนั้น แล้วเลือก Edit Settings จากนั้นดุในส่วน API Settings ให้เราคัดลอกค่า Client secret และ Package SID ไว้
จากนั้นกลับไปเปิด Windows Azure Management Portal เข้าไปที่แท็บ Push ของ Mobile Service ที่เราสร้างขึ้น จากนั้นนำ Client secret และ Package SID ดังกล่าวมากรอกที่หน้านี้
หมายเหตุ 1: เนื่องจากการลงทะเบียนแอพลิเคชันใน Windows 8 App Store จะไปสร้างแอพลิเคชันใหม่ในระบบ Live Connect Developer Center ให้โดยอัตโนมัติ ดังนี้ในกรณีที่แอพลิเคชันของเราเชื่อมต่อกับ Microsoft Account ตามที่กล่าวไว้ในบทความตอนที่แล้ว ให้เปลี่ยน Client secret จากแอพลิเคชันที่สร้างเองจากตอนที่แล้ว มาใช้ Client secret จากแอพลิเคชันใหม่ที่ถูกสร้างโดยอัตโรมัตินี้แทนด้วย
หมายเหตุ 2: หากทำตามบทความนี้แล้วไม่สามารถรับส่ง Push Notification ได้สำเร็จ โดยพบข้อความผิดพลาดในหน้า Logs ของ Mobile Service ในลักษณะ Error: The cloud service is not authorized to send a notification to this URI even though they are authenticated. ให้ทดลองรีเซทค่า Client secret ของแอพลิเคชันใน Live Connect Developer Center ใหม่
เมื่อเริ่มแอพลิเคชัน ให้แอพลิเคชันส่ง URI ที่จะใช้ในการรับการเตือนไปให้ Mobile Service เก็บไว้ก่อน จากนั้นเมื่อมีเหตุการณ์ที่ต้องการเตือนผู้ใช้จึงค้น URI ที่ต้องการเตือนออกมาสั่งให้ push notification ไปยัง URI นั้นๆ
ก่อนอื่นเราจะต้องสร้างตาราง PushChannel ขึ้นมาก่อน เพื่อเก็บ URI ที่จะใช้ในการเตือน โดยกำหนดสิทธิ์ให้ผู้ใช้ที่ยืนยันตัวตนแล้ว insert ได้ และไม่ยอมให้ทำอะไรอย่างอื่น
จากนั้นเข้าไปที่ตาราง PushChannel แล้วแก้ไขสคริปท์ในการแทรกข้อมูล URI ใหม่ โดยเราจะเก็บ URI คู่กับ ID ของผู้ใช้ เพื่อให้เราสามารถค้นหา URI ที่ต้องการเตือนโดยดูจากผู้ใช้ที่จะเตือนได้ในภายหลัง (โดยไม่เก็บข้อมูลซ้ำหากมีข้อมูลอยู่แล้ว) ดังนี้
{syntaxhighlighter brush: jscript}
function insert(item, user, request) {
tables.getTable('PushChannel')
.where({ uri: item.uri })
.read({
success: function(existingChannels) {
if (existingChannels.length > 0) {
request.respond(200, existingChannels[0]);
}
else {
item.userId = user.userId;
request.execute();
}
}
});
}{/syntaxhighlighter}
สำหรับตัวแปรและฟังก์ชันต่างๆ ของสคริปท์บน Mobile Service ที่ไม่ได้อธิบายรายละเอียดในที่นี้นั้น สามารถดูรายละเอียดวิธีการเรียกใช้ได้จากเอกสาร Mobile Services server script reference
จากนั้นแก้ไขซอร์สโค้ดในไฟล์ /js/default.js ของแอพลิเคชัน เพื่อให้สร้าง URI ออกมา และส่งไปให้ Mobile Service เก็บไว้ ดังนี้ (บรรทัดที่เพิ่มเติมแสดงด้วยการไฮไลท์)
{syntaxhighlighter brush: jscript highlight: [46,47,48,49,50,51,52,53,54]}
//
(function () {
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var nav = WinJS.Navigation;
var passwordVault = new Windows.Security.Credentials.PasswordVault();
var azureClient = new Microsoft.WindowsAzure.MobileServices.MobileServiceClient(
"https://strifepad.azure-mobile.net/",
"vxhSfbaGkOASriBUGeXZJtMPregQGy91"
);
WinJS.Namespace.define("StrifePad", {
azureClient: azureClient
});
var startApp = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (app.sessionState.history) {
nav.history = app.sessionState.history;
}
args.setPromise(WinJS.UI.processAll()
.then(function () {
if (nav.location) {
nav.history.current.initialPlaceholder = true;
return nav.navigate(nav.location, nav.state);
} else {
return nav.navigate(Application.navigator.home);
}
})
);
}
else if (args.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.protocol) {
args.setPromise(WinJS.UI.processAll()
.then(function () {
if (args.detail.uri.host == "document") {
return nav.navigate("/pages/editor/editor.html", {
uri: args.detail.uri
});
}
else {
return nav.navigate(Application.navigator.home);
}
})
);
}
// Create URI for push notification
var channelOperation = Windows.Networking.PushNotifications
.PushNotificationChannelManager
.createPushNotificationChannelForApplicationAsync()
.then(function (newChannel) {
azureClient.getTable("PushChannel").insert({
uri: newChannel.uri
});
});
}
var ensureLoggedInBeforeStartApp = function (args) {
try {
var credentials = passwordVault.findAllByResource("azureClient");
credentials[0].retrievePassword();
StrifePad.azureClient.currentUser = {
userId: credentials[0].userName,
mobileServiceAuthenticationToken: credentials[0].password
};
startApp(args);
} catch (e) {
StrifePad.azureClient.login("microsoftaccount")
.then(function (results) {
var credential = new Windows.Security.Credentials.PasswordCredential(
"azureClient",
results.userId,
results.mobileServiceAuthenticationToken);
passwordVault.add(credential);
// Logging in to Azure mobile service completed.
// We'll continue the normal process to let the application starts.
startApp(args);
}, function () {
// Logging in to Azure mobile service failed. Try again.
var tryAgainMessage = new Windows.UI.Popups.MessageDialog(
"Sorry. You have to login to use the application",
"Login required"
);
return tryAgainMessage.showAsync()
.then(function () {
return ensureLoggedInBeforeStartApp(args);
});
});
}
};
app.addEventListener("activated", function (args) {
ensureLoggedInBeforeStartApp(args);
});
app.addEventListener("checkpoint",function (args) {
app.sessionState.history = nav.history;
});
app.start();
})();{/syntaxhighlighter}
หากทดลองรันแอพลิเคชัน แล้วเปิดดูข้อมูลในตาราง PushChannel จะพบข้อมูล URI สำหรับเตือนและ ID ของผู้ใช้เพิ่มขึ้นมาในฐานข้อมูลแล้ว
เพื่อให้ฐานข้อมูลคิวรีข้อมูลได้รวดเร็วยิ่งขึ้น เราอาจเข้าไปทำ index ให้กับฟิลด์ uri และฟิลด์ userId เพิ่มเติม เนื่องจากจะมีการคิวรีด้วยฟิลด์ทั้งสองบ่อยครั้ง
ก่อนอื่นเราจะแก้ไขแอพลิเคชันในไฟล์ /pages/editor/editor.js เพื่อให้เก็บ ID ของเอกสารที่กำลังแก้ไขอยู่ และกำหนดให้หากแก้ไขเอกสารแล้วกดปุ่มบันทึก ให้สร้างเอกสารใหม่ โดยส่งฟิลด์ originalDocumentId ระบุ ID ของเอกสารต้นฉบับด้วย ดังนี้ (บรรทัดที่เพิ่มเติมแสดงด้วยการไฮไลท์)
{syntaxhighlighter brush: jscript highlight: [3,12,22,23,24,25,26,27,28,29,30,31,32,33,62]}
(function () {
"use strict";
var currentDocumentId;
var pageTitle;
var contentArea;
var bottomAppBar;
var saveFlyout;
var saveFlyoutNameField;
var saveFlyoutSubmitButton;
var onCheckpoint = function () {
WinJS.Application.sessionState.editorState = {
currentDocumentId: currentDocumentId,
pageTitle: pageTitle.innerText,
innerText: contentArea.innerText,
selectionStart: contentArea.selectionStart,
selectionEnd: contentArea.selectionEnd
}
};
var onSubmitSaveFlyout = function () {
bottomAppBar.winControl.hide();
saveFlyout.winControl.hide();
var newDocument = {
name: saveFlyoutNameField.value,
content: contentArea.innerText
};
if (currentDocumentId) {
newDocument.originalDocumentId = currentDocumentId;
}
StrifePad.azureClient.getTable("Document").insert(newDocument)
.done(function (result) {
currentDocumentId = result.id;
pageTitle.innerText = saveFlyoutNameField.value;
});
};
WinJS.UI.Pages.define("/pages/editor/editor.html", {
ready: function (element, options) {
pageTitle = element.querySelector("#pageTitle");
contentArea = element.querySelector("#contentArea");
bottomAppBar = element.querySelector("#bottomAppBar");
saveFlyout = element.querySelector("#saveFlyout");
saveFlyoutNameField = element.querySelector("#saveFlyoutNameField");
saveFlyoutSubmitButton = element.querySelector("#saveFlyoutSubmitButton");
var appData = Windows.Storage.ApplicationData.current;
contentArea.spellcheck = appData.localSettings.values["checkSpelling"] || false;
if (WinJS.Application.sessionState.editorState) {
var editorState = WinJS.Application.sessionState.editorState;
pageTitle.innerText = editorState.pageTitle;
contentArea.innerText = editorState.innerText;
contentArea.setSelectionRange(editorState.selectionStart, editorState.selectionEnd);
delete WinJS.Application.sessionState.editorState;
}
else if (options && options.uri) {
var documentId = options.uri.path.substring(1);
StrifePad.azureClient.getTable("Document")
.where({
id: documentId
})
.take(1)
.read()
.then(function (result) {
if (result.length > 0) {
currentDocumentId = result[0].id;
pageTitle.innerText = result[0].name;
contentArea.innerText = result[0].content;
}
});
}
saveFlyoutSubmitButton.addEventListener("click", onSubmitSaveFlyout);
WinJS.Application.addEventListener("checkpoint", onCheckpoint);
}
});
})();{/syntaxhighlighter}
จากนั้นแก้ไขสคริปท์ในการแทรกข้อมูลของตาราง Document ใน Mobile Service ใหม่เป็นดังนี้
{syntaxhighlighter brush: jscript}
function insert(item, user, request) {
if (!item.name) {
request.respond(statusCodes.BAD_REQUEST, 'Name must be defined');
}
item.ownerUserId = user.userId;
item.createdAt = new Date();
request.execute({
success: function(newDocuments) {
// Write to the response and then send the notification in the background
request.respond();
if (item.originalDocumentId) {
notifyForkByOriginalDocumentId(item.originalDocumentId, item.name, item.id);
}
}
});
}
function notifyForkByOriginalDocumentId(forkedDocumentId, newDocumentName, newDocumentId) {
tables.getTable('Document')
.where({
id: forkedDocumentId
})
.read({
success: function(documents) {
if (documents.length > 0) {
var forkedDocument = documents[0];
notifyForkByUserId(forkedDocument.ownerUserId, forkedDocument.name, newDocumentName, newDocumentId);
}
}
});
}
function notifyForkByUserId(userId, forkedDocumentName, newDocumentName, newDocumentId) {
tables.getTable('PushChannel')
.where({
userId: userId
})
.read({
success: function(channels) {
notifyForkByChannels(channels, forkedDocumentName, newDocumentName, newDocumentId);
}
});
}
function notifyForkByChannels(channels, forkedDocumentName, newDocumentName, newDocumentId) {
var notificationBody = '"';
notificationBody += forkedDocumentName;
notificationBody += '" is forked to "';
notificationBody += newDocumentName;
notificationBody += '"';
var launchUri = 'strifepad://document/' + newDocumentId;
channels.forEach(function(channel) {
push.wns.sendToastText02(channel.uri, {
text1: 'Your document is forked',
text2: notificationBody
}, {
launch: launchUri,
duration: 'long',
error: function(e) {
if (e.statusCode == 410) {
// 410 Gone = Channel expired
tables.getTable('PushChannel').del(channel.id);
}
}
});
});
}{/syntaxhighlighter}
ถึงสคริปท์นี้จะดูค่อนข้างยาว แต่ใจความสำคัญมีเพียงสคริปท์นี้มีเพียงแค่ เราจะสร้างเอกสารใหม่ให้เรียบร้อย จากนั้นเราจะเรียกฟังก์ชัน request.respond(); ทันทีเพื่อให้ Mobile Service ตอบกลับไปยังแอพลิเคชันเสียก่อน แล้วจึงปล่อยให้ Mobile Service ทำงานเกี่ยวกับ Push Notification ต่อไป โดยค้นหาว่าเอกสารต้นฉบับนั้น มีผู้ใช้คนใดเป็นเจ้าของ จากนั้นจึงนำ ID ของผู้ใช้ที่เป็นเจ้าของไปหา URI ที่จะต้องเตือนจากตาราง PushChennel ที่เราเตรียมไว้ แล้วส่งข้อความเตือนออกไป หากพบว่า URI ที่ใช้ส่ง Push Notification นั้นหมดอายุให้ลบออกจากฐานข้อมูลด้วย
สำหรับฟังก์ชันที่ใช้สำหรับ Push Notification นั้น นอกจาก push.wns.sendToastText02 ตามตัวอย่างแล้ว ยังมีฟังก์ชันอื่นๆ โดยสามารถดูรายละเอียดได้จากเอกสารวัตถุ push.wns
ถึงจุดนี้ ถ้าเราทดสอบโดยสร้างเอกสารใหม่ จากนั้นสร้างเอกสารใหม่ซ้ำอีกครั้งจากเอกสารที่เพิ่งสร้างไป จะพบข้อความเตือนแสดงที่มุมขวาบนของหน้าจอ เนื่องจากเรากำลัง fork เอกสารของตัวเองอยู่นั่นเอง
โดยปกติเมื่อเราพัฒนาแอพลิเคชันเสร็จแล้ว เราจะต้องสร้างชุดติดตั้งรวมถึงเตรียมการขายด้วยตัวเองทั้งหมด แต่สำหรับ Windows 8 ซึ่งเป็นระบบปฏิบัติการ Windows รุ่นแรกที่มีผู้ใช้สามารถเลือกซื้อและติดตั้งแอพลิเคชันได้จาก Store ได้เลย เราจะสามารถเผยแพร่แอพลิเคชันได้ง่ายขึ้นเป็นอย่างมาก ในตอนนี้เราจะแนะนำแอพลิเคชันที่พัฒนาเสร็จเรียบร้อยแล้วขึ้นไปขายบน Windows 8 App Store กัน
ก่อนเราจะส่งแอพลิเคชันขึ้นสู่ Windows 8 App Store นั้น เราควรเตรียมแอพลิเคชันของเราให้เรียบร้อย โดยมีคำแนะนำดังนี้
ควรการกำหนดลักษณะต่างๆ ของแอพลิเคชัน เช่น ชื่อที่จะใช้แสดงผล การรองรับการพลิกหน้าจอแบบต่างๆ ภาพไอคอนของแอพลิเคชัน โดยเปิดไฟล์ package.appxmanifest แล้วแก้ไขข้อมูลในแท็บ Application UI
ควรทดสอบแอพลิเคชันในขนาดหน้าจอต่างๆ โดยเราสามารถเลือกทดสอบแอพลิเคชันของเราบนเครื่องจำลอง (simulator) ได้ โดยคลิกที่ลูกศรด้านหลังปุ่มดีบั๊กโปรแกรม แล้วเลือก Simulator
การส่งแอพลิเคชันขึ้นสู่ Windows 8 App Store นั้นจะต้องใช้ Windows Developer Account ซึ่งเป็นคนละบัญชีกับที่เราได้สมัครไปในตอนก่อนๆ สำหรับผู้ที่ไม่มีบัญชีนี้สามารถสมัครได้ โดยเข้าไปที่ Windows Dev Center เพื่อสมัคร Windows Developer Account โดยมีค่าใช้จ่ายในการสมัคร 1,500 บาท (แนะนำให้จ่ายเงินผ่านบัตรเครดิตหรือบัตรเดบิตปกติ เนื่องจากผู้เขียนทดลองใช้หมายเลขบัตรเครดิตสำหรับซื้อสินค้าบนอินเทอร์เน็ตแล้วไม่สามารถจ่ายได้) สามารถเลือกสมัครบัญชีสำหรับนักพัฒนาอิสระหรือบัญชีสำหรับองค์กรก็ได้
เมื่อสมัคร Windows Developer Account เรียบร้อยแล้วจะพบกับหน้าแดชบอร์ดพร้อมให้เราส่งแอพลิเคชันต่อไป
คลิกที่ลิงก์ Submit an app จากเมนูด้านซ้ายมือ จะพบหน้าแสดงขั้นตอนการส่งแอพลิเคชัน โดยขั้นตอนที่ต้องทำให้เสร็จคือขั้นตอนแรก นั่นคือการตั้งชื่อแอพลิเคชัน ส่วนขั้นตอนอื่นๆ เราสามารถมาทำต่อในภายหลังได้
เมื่อคลิกที่ขั้นตอน App name ระบบจะให้เราตั้งชื่อแอพลิเคชัน ชื่อแอพลิเคชัน บน Windows 8 App Store นั้นเราไม่สามารถตั้งชื่อแอพลิเคชันซ้ำกันได้ โดยเราสามารถจองชื่อสำหรับแอพลิเคชันของเราไว้ล่วงหน้าก่อนส่งแอพลิเคชันจริงได้ถึง 1 ปี
หลังจากลงทะเบียนแอพลิเคชันเรียบร้อยแล้ว เราจะเชื่อมโยงโปรเจกต์ใน Visual Studio เข้ากับแอพลิเคชันที่ลงทะเบียนไว้ เพื่อให้โปรเจกต์ใน Visual Studio มีค่าต่างๆ เช่น ชื่อแพคเกจ หมายเลขรุ่น ตรงกับในระบบ
โดยคลิกขวาที่โปรเจกต์แล้วเลือกเมนู Store > Assosiate App with The Store จากนั้นล็อกอินด้วย Windows Developer Account แล้วเลือกชื่อแอพลิเคชันที่ลงทะเบียนไว้จากขั้นตอนที่แล้ว
ขั้นตอนถัดๆ มาในการส่งแอพลิเคชันเข้าสู่ระบบ เราจะต้องกำหนดคุณสมบัติต่างๆ ของแอพลิเคชัน ดังนี้
ขั้นตอนกำหนดรายละเอียดการขาย (Selling details) ให้เรากำหนดราคาแอพลิเคชัน ซึ่งระดับราคามีตั้งแต่ 50 บาท (1.49 USD) ไปจนถึง 31,000 บาท (รวมภาษีมูลค่าเพิ่มของแต่ละประเทศ) และกำหนดช่วงเวลาที่จะอนุญาตให้ทดลองใช้แอพลิเคชันได้ (1 วัน / 7 วัน / 15 วัน / 30 วัน / ตลอดไป) เลือกประเทศที่จะวางขายแอพลิเคชัน และวันที่จะวางขายแอพลิเคชัน โดยเราจะเลือกขายทันทีที่แอพลิเคชันผ่านการตรวจสอบ หรือจะให้รอจนกว่าจะถึงวันที่เรากำหนดก็ได้ ตัวเลือกนี้จะมีประโยชน์มากในกรณีที่ต้องการวันเปิดตัวแอพลิเคชันที่แน่นอน
นอกจากนี้เรายังสามารถกำหนดให้แอพลิเคชันของเราเปิดให้ทดลองใช้เฉพาะบางฟีเจอร์ หรือขายบางฟีเจอร์ในแอพลิเคชัน (In-app purchase) ด้วยก็ได้ โดยจะต้องเพิ่มโค้ดในการตรวจสอบสิทธิ์ต่างๆ ด้วย รายละเอียดสามารถอ่านเพิ่มเติมได้จากเอกสาร Choosing your business model
นอกจากนี้เราจะต้องกำหนดหมวดหมู่ของแอพลิเคชัน และคุณสมบัติของเครื่องที่จะใช้แอพลิเคชันได้เล็กๆ น้อยๆ อีกด้วย คือใช้ฟีเจอร์พิเศษใน DirectX หรือไม่ และจำเป็นต้องมีแรมขั้นต่ำเท่าไร (มีให้เลือก ไม่ต้องการขั้นต่ำ/ต้องการ 2GB ขึ้นไป)
ถัดมาเป็นขั้นตอน Advanced features ในขั้นตอนนี้ให้เราตั้งค่าฟีเจอร์เกี่ยวกับ push notification, การเชื่อมต่อกับ Microsoft Account หรือฟีเจอร์พิเศษที่จะเปิดเป็น In-app purchase ก็สามารถกำหนดได้ในขั้นตอนนี้
จากนั้นจะเป็นขั้นตอน Cryptography ซึ่งจะให้เราตอบคำถามเกี่ยวกับการทำงานของแอพลิเคชันว่ามีการเข้ารหัสหรือไม่ ซึ่งอาจส่งผลกระทบต่อการเผยแพร่แอพลิเคชันในบางประเทศ เนื่องจากเหตุผลทางข้อกฎหมาย (อ่านรายละเอียดเพิ่มที่นี่ แต่สรุปได้คร่าวๆ คือแอพลิเคชันสามารถเข้ารหัสได้กับรหัสผ่าน, การยืนยันตัวตน, การป้องกันการทำซ้ำ, DRM, และ Digital Signature เท่านั้น นอกเหนือจากนี้จะต้องมีหมายเลข ECCN อย่างไรก็ตาม กรณีส่วนใหญ่ไม่จำเป็นต้องใช้หมายเลขนี้)
ขั้นตอนถัดมาคือ Age rating and rating certificates ให้เรากำหนดระดับอายุผู้ใช้ตามเกณฑ์ของ Windows 8 App Store โดยหากไม่มั่นใจแนะนำว่าให้กำหนดระดับอายุ 12+ ไว้ก่อน นอกจากนี้หากแอพลิเคชันเราเป็นเกม เราสามารถอัพโหลดไฟล์ยืนยันระดับอายุผู้ใช้จากหน่วยงานต่างๆ ได้ เพื่อให้ระบบแสดงไอคอนระดับอายุนั้นๆ รวมถึงทำให้เกมขายได้ในประเทศที่บังคับให้เราต้องผ่านการประเมินด้วย
เมื่อแอพลิเคชันเสร็จเรียบร้อยและพร้อมส่งขึ้นสโตร์ ให้เราคลิกขวาที่โปรเจกต์ใน Visual Studio และเลือก Store > Create App Package... เพื่อสร้างแพคเกจสำหรับส่งแอพลิเคชัน โดยเราจะต้องเลือกชื่อแอพลิเคชันที่เราสร้างไว้ และเลือกจะสร้างแพคเกจสำหรับซีพียูสถาปัตยกรรมใดบ้าง (เนื่องจากโปรเจกต์ตัวอย่างเขียนด้วยภาษา JavaScript เราจึงสามารถสร้างแพคเกจเดียวสำหรับทุกสถาปัตยกรรมได้เลย)
เมื่อสร้างแพคเกจเสร็จเรียบร้อยแล้ว ก่อนจะนำแพคเกจไปส่ง เราสามารถใช้เครื่องมือ Windows App Certification Kit ตรวจสอบเบื้องต้นก่อนได้ โดยเครื่องมือนี้จะนำแอพลิเคชันของเรามาตรวจสอบและทดลองรัน ขั้นตอนนี้จะใช้เวลาพอสมควรและไม่ควรใช้เครื่องระหว่างที่กำลังตรวจสอบอยู่
หากตรวจสอบแล้วไม่พบปัญหาใดๆ ให้แก้ไข ก็สามารถนำแพคเกจที่สร้างขึ้นส่งขึ้นไปบน Windows App Store ได้เลย โดยกลับไปที่หน้าแอพลิเคชันที่สร้างไว้ใน Windows Dev Center เลือกขั้นตอน Packages และอัพโหลดแพคเกจที่สร้างขึ้นไปในระบบ
จากนั้นกำหนดคำหลัก และคำอธิบายของแอพลิเคชัน รวมถึงภาพประกอบที่จะปรากฎใน Windows App Store ในขั้นตอน Description สุดท้ายหากมีคำอธิบายใดๆ ที่ต้องอธิบายให้ผู้ตรวจแอพลิเคชันก่อนขึ้น Windows App Store ทราบ ก็ระบุไว้ในขั้นตอน Note to tester
อ่านรายละเอียดเพิ่มเติมเกี่ยวกับการตั้งชื่อ ระบุคำอธิบายต่างๆ ได้จากเอกสารหัวข้อ Creating a great app listing และ Avoiding common certification failures
เมื่อทุกขั้นตอนเรียบร้อยแล้วก็คลิกปุ่ม Submit for certification เพื่อส่งแอพลิเคชันให้ตรวจสอบ โดยระหว่างนี้เราสามารถตรวจสอบได้ว่าดำเนินการไปถึงขั้นตอนไหนแล้วอีกด้วย
บทความชุดการพัฒนาแอพลิเคชันสำหรับ Windows 8 App Store ทั้งหกตอนได้นำเสนอความสามารถพื้นฐานที่จำเป็นต่อการพัมนาแอพลิเคชันบน Windows 8 App Store แล้ว แต่ยังมีความสามารถอีกมากที่ไม่ได้กล่าวถึงในบทความชุดนี้ ซึ่งผู้อ่านสามารถศึกษาเพิ่มเติมได้จากหนังสือจาก Microsoft Pressซึ่งสามารถดาวน์โหลดได้ฟรี และดาวน์โหลดแพ็กตัวอย่างฟีเจอร์ต่างๆ ของแอพลิเคชันบน Windows 8 ได้จาก MSDN
ขอให้มีความสุขกับการพัฒนาแอพลิเคชันสำหรับ Windows 8 ครับ
บทความชุด การเขียนแอพลิเคชันสำหรับ Windows 8 App Store