從PaaS到IaaS,Windows Azure在雲端技術上不斷的精進及擴充支援範圍,由原本只能使用.NET技術的雲端平台,慢慢的轉向支援多樣化技術,現在的Windows Azure不僅能使用.NET技術,
也能使用Java、PHP、Phyton甚至是C++,堪稱是目前在技術容納度上最開放的雲端技術。
文/黃忠成
What’s Windows Azure Mobile Services
從PaaS到IaaS,Windows Azure在雲端技術上不斷的精進及擴充支援範圍,由原本只能使用.NET技術的雲端平台,慢慢的轉向支援多樣化技術,現在的Windows Azure不僅能使用.NET技術,
也能使用Java、PHP、Phyton甚至是C++,堪稱是目前在技術容納度上最開放的雲端技術。
當然,就IaaS角度上而言,在上面使用何種技術並不會受到任何限制,但雲端平台能支援的也僅僅只有IaaS的範疇而已,像Windows Azure這種除了原本IaaS的範疇之外,還努力的讓多樣化的
技術能適應於本家提出的PaaS技術的雲端方案提供者,其實是非常稀少的。
在相關的基礎建設及服務穩定發展後,Windows Azure也開始朝更高階的加值服務發展,例如前陣子提出的Media Servcies及本文主軸的Mobile Services,這種加值服務的特點在現有的基礎建設及服務,
加以重新包裝及整合,變成在IaaS、PaaS外的另一種服務型態。
要上雲端,你不一定得自己學會ASP.NET或是Java來架網站,不一定得知道當網站流量變大時,該使用那些技術來解決,好的雲端服務廠商會幫你鋪好由頭至尾的路,用這種角度來看Windows Azure
所提供的Media Services及Mobile Services再恰當不過了。
Mobile Services整合了SQL Azure、PaaS及Access Control Services,聚焦的是開發行動App的族群(Android、Windows Phone、iOS、Windows 8),舉個例子來說,當你想開發一個美食導覽的App,
一開始是由自己蒐集知道的美食餐廳等資料,但這畢竟是片面的,為了讓資料更加全面及完整,你自然會想把所有的資料放在雲端上,而使用者可以透過App來瀏覽及編修、對某些餐廳下評價,你最後只要由眾多評價來取平均值即可。
就以上的構想,App必定需要一個後端服務,這可能是一個虛擬主機,裡頭放了網站跟資料庫,也可能是兩個虛擬主機,一個架網站,另一個放資料庫,大致的架構會如下圖。
圖1
網站主要是提供Web Services讓App連上來,然後將使用者新增、修改或是評價的資料寫入資料庫,因此,開發者必須懂得如何撰寫Web Services或是REST,也必須知道如何連到資料庫,這是技術面。
就成本面來看,虛擬主機加上一個”可用”(排除檔案型資料庫)的資料庫花費並不便宜,對於小成本,看不到未來獲利的App產業,這種方案雖然貴,但也是不得不付的費用。
Windows Azure Mobile Services便是聚焦於此類族群,你只需要擁有一個Windows Azure帳號,建立一個Mobile Services後,即可擁有一個網址,一個SQL Azure資料庫(20MB),整合帳號驗證,
及Push Notification的支援,與自行架設虛擬主機不同的是,你不需要自己建立或管理資料庫,也不需要撰寫Web Services與資料庫串接的程式碼,App只需要使用Mobile Servcies所提供的SDK即可對資料庫進行新增、
修改等作業,當然,這只是Mobile Services一部分的功能,更深入了解後你會發現Mobile Services提供了很多方便的服務,目標就是簡化App開發者自行處理後端例如資料庫、排程、驗證、Push Notification的工作。
圖2
開始建立 Mobile Services
在申請Windows Azure帳號後,登入manage.windowsazure.com入口網站即可開始建立Mobile Services網站。
圖3
點擊Mobile Services連結後會出現以下畫面。
圖4
點擊那個箭頭即可開始建立Mobile Services網站。
圖5
這裡請輸入你想要的URL,注意,一定是.azure-mobile.net結尾,以本例來說就是myrest.azure-mobile.net,接著是選擇要建立新的SQL Database(20 MB免費),還是建立另一個非免費的SQL Database,
下方則是此服務所在的資料中心,這會影響你的使用者連上來的速度,一般在亞洲的話就選擇East Asia比較快,按下箭頭後進入下一步。
圖6
這裡是調整資料庫的安全設定,主要是管理員帳號及密碼,完成後這個Mobile Services就開始建立。
圖7
等到Status顯示為Ready就算完成建立,可以開始管理這個Mobile Services或撰寫程式了。
點進去後可以看到豐富的管理介面,日後有機會我們在慢慢介紹這些功能。
圖8
First Application
現在讓我們實際開發一個簡單的應用程式,請切到DATA頁面,這裡可以新增資料表,並設定存取權限。
圖9
請建立一個Customers資料表。
圖10
權限分成下圖四種。
圖11
第一個是允許所有人存取,接著是只允許擁有Application Key的人存取、只允許認證過後的使用者存取、只允許管理者和內部Script存取,這裡請選擇”只允許擁有Application Key的人存取”這個選項,
其它本文會在後面解釋用途及時機。完成後可看到以下畫面。
圖12
點進去可以到達簡單的資料表管理介面,這裡除了可以瀏覽資料、管理資料表的Schema、權限外,還可以添加CRUD時的SCRIPT,這個我們日後再談。
圖13
完成資料表的建立後,就可以開始撰寫程式了,此處以Windows Phone 8為例,先建立一個Windows Phone 8專案,接著在專案上按右建,透過NuGet來添加Windows Azure Mobile Services SDK所需要的Assemblys。
圖14
圖15
接著到Mobile Services的Dashboard頁面取得網站的URL。
圖16
然後把URL的資訊添加到App.xaml.cs中。
using System;
using System.Diagnostics;
using System.Resources;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using PhoneApp18.Resources;
using Microsoft.WindowsAzure.MobileServices;
namespace PhoneApp18
{
public partial class App : Application
{
public static MobileServiceClient MobileService = new MobileServiceClient("https://myrest.azure-mobile.net/", "AppKey");
………………..
這裡的AppKey指的是Application Key,由於我們設定此資料表為限制擁有Application Key才能存取,因此得先由Mobile Services的Dashboard頁面取得Application Key再填入這裡。
圖17
圖18
完成後的MobileServiceClient建構式大概是像下面這樣。
public partial class App : Application
{
public static MobileServiceClient MobileService = new MobileServiceClient("https://myrest.azure-mobile.net/", "WGoPUaxxxxxxxxfM31");
然後新增一個類別,用來對應資料表。
DataModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PhoneApp18
{
public class Customers
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Tel { get; set; }
}
}
如果要取得此資料表中的資料,可以用下列的方式。
namespace PhoneApp18
{
public partial class MainPage : PhoneApplicationPage
{
private IMobileServiceTable _customersTable = App.MobileService.GetTable();
// Constructor
public MainPage()
{
InitializeComponent();
InitData();
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
public async void InitData()
{
lst1.ItemsSource = await _customersTable.ToCollectionAsync();
}
...............
新增、修改、刪除則可以用下列的方式。
public async void InsertCustomer(Customers c)
{
await _customersTable.InsertAsync(c);
}
public async void DeleteCustomer(Customers c)
{
await _customersTable.DeleteAsync(c);
}
public async void UpdateCustomer(Customers c)
{
await _customersTable.UpdateAsync(c);
}
private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
Customers c = new Customers() { Name = "code6421", Address = "Taiwan", Tel = "123948493" };
InsertCustomer(c);
}
對於資料表的查詢作業,Mobile Services已經與LINQ整合在一起,所以用下列的LINQ運算式便可查到資料。
lst1.ItemsSource = await _customersTable.Where(a => a.Name == "code6421").ToCollectionAsync();
如你所見,Windows Azure Mobile Services把資料存取的工作變得相當簡單,開發者不需要撰寫任何後端程式就可以存取雲端上資料庫的資料,且不需要把資料庫暴露在網路上。
其實資料存取服務只是一個入口,Mobile Services以此為中心,往外發展提供了Push Notification、Schedule Tasks等相當有用的服務,讓開發App的人更方便,後續的文章會持續介紹這些服務。
Working with Android
Windows Azure Mobile Service的服務自然也支援其它平台,本節以Android為例,首先用Eclipse建立一個Android Project,接著到以下網址下載Windows Azure Mobile Service SDK for Android。
http://go.microsoft.com/fwlink/p/?linkid=280126&clcid=0x409
這是一個.zip檔,結構如下圖。
圖19
裡面包含了存取Mobile Services及Push Notification所需要的類別庫,請將這兩個目錄中的.jar檔案放到Android Project的lib目錄下。
圖20
接著由Eclipse上Refresh這個libs目錄即可引入這些Librarys。然後建立對應資料表的類別。
Customers.java
package com.gis.amservicedemo;
public class Customers {
@com.google.gson.annotations.SerializedName("id")
private int _id;
@com.google.gson.annotations.SerializedName("Name")
private String _name;
@com.google.gson.annotations.SerializedName("Address")
private String _address;
@com.google.gson.annotations.SerializedName("Tel")
private String _tel;
public int getId() {
return _id;
}
public final void setId(int id) {
_id = id;
}
public String getName(){
return _name;
}
public final void setName(String value){
_name = value;
}
public String getAddress(){
return _address;
}
public final void setAddress(String value){
_address = value;
}
public String getTel(){
return _tel;
}
public final void setTel(String value){
_tel = value;
}
public Customers(){
}
}
修改主UI。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="false"
android:layout_centerVertical="false" >
</ListView>
</RelativeLayout>
我們使用ListView來顯示資料,所以要建立對應的Adatper。
CustomersAdapter.java
package com.gis.amservicedemo;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class CustomersAdapter extends ArrayAdapter {
Context mContext;
int mLayoutResourceId;
public CustomersAdapter(Context context, int layoutResourceId) {
super(context, layoutResourceId);
mContext = context;
mLayoutResourceId = layoutResourceId;
}
/**
* Returns the view for a specific item on the list
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
final Customers currentItem = getItem(position);
if (row == null) {
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(mLayoutResourceId, parent, false);
}
final TextView text = (TextView) row.findViewById(android.R.id.text1);
text.setText(currentItem.getName());
return row;
}
}
接著是主UI的程式碼。
MainActivity.java
package com.gis.amservicedemo;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import com.microsoft.windowsazure.mobileservices.MobileServiceClient;
import com.microsoft.windowsazure.mobileservices.MobileServiceTable;
import com.microsoft.windowsazure.mobileservices.ServiceFilterResponse;
import com.microsoft.windowsazure.mobileservices.TableQueryCallback;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.view.Menu;
import android.widget.ListView;
public class MainActivity extends Activity {
private MobileServiceClient _client;
private MobileServiceTable _table;
private CustomersAdapter _adapter;
public List _list = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
// Create the Mobile Service Client instance, using the provided
// Mobile Service URL and key
_client = new MobileServiceClient(
"https://myrest.azure-mobile.net/", "<app key>",
this);
// Get the Mobile Service Table instance to use
_table = _client.getTable(Customers.class);
} catch (MalformedURLException e) {
createAndShowDialog(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
}
_adapter = new CustomersAdapter(this, android.R.layout.simple_list_item_1);
ListView listViewToDo = (ListView) findViewById(R.id.listView1);
listViewToDo.setAdapter(_adapter);
refreshItemsFromTable();
}
private void createAndShowDialog(Exception exception, String title) {
createAndShowDialog(exception.toString(), title);
}
private void refreshItemsFromTable() {
_table.execute(new TableQueryCallback() {
@Override
public void onCompleted(List result, int count,
Exception exception, ServiceFilterResponse response) {
if(exception == null){
_adapter.clear();
for (Customers item : result) {
_adapter.add(item);
}
}
}
});
}
/**
* Creates a dialog and shows it
*
* @param message
* The dialog message
* @param title
* The dialog title
*/
private void createAndShowDialog(String message, String title) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(message);
builder.setTitle(title);
builder.create().show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
執行畫面如下。
圖21
大概有幾個重點,首先是MobileServcieClient物件的建立。
try {
// Create the Mobile Service Client instance, using the provided
// Mobile Service URL and key
_client = new MobileServiceClient(
"https://myrest.azure-mobile.net/", "<app key>",
this);
// Get the Mobile Service Table instance to use
_table = _client.getTable(Customers.class);
} catch (MalformedURLException e) {
createAndShowDialog(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
}
要與Mobile Services溝通並存取資料表必須要先建立這個MobileServiceClient物件,你可以把它當作是一個入口,任何動作都是由這裡開始的,注意,必須傳入URL及Application Key。
另一個重點是取得資料。
private void refreshItemsFromTable() {
_table.execute(new TableQueryCallback() {
@Override
public void onCompleted(List result, int count,
Exception exception, ServiceFilterResponse response) {
if(exception == null){
_adapter.clear();
for (Customers item : result) {
_adapter.add(item);
}
}
}
});
}
Mobile Services SDK設計成非同步樣式,每個動作基本上都會有成功與失敗的callback class,呼叫者必須負責定義它們。
另外,直接呼叫execute可以取得所有資料,當然,你也可以呼叫where來過濾資料,像是下面這個樣子。
_table.where().field("Name").eq("code6421").execute(new TableQueryCallback() {
@Override
public void onCompleted(List result, int count,
Exception exception, ServiceFilterResponse response) {
if(exception == null){
_adapter.clear();
for (Customers item : result) {
_adapter.add(item);
}
}
}
});
相關的文件可參考以下網址。
http://dl.windowsazure.com/androiddocs/
新增資料可以透過下列方式 。
Customers item = …….
_table.insert(item, new TableOperationCallback() {
public void onCompleted(ToDoItem entity, Exception exception, ServiceFilterResponse response) {
if (exception == null) {
mAdapter.add(entity);
} else {
createAndShowDialog(exception, "Error");
}
}
});
同理,呼叫delete、update函式可以刪除或是更新資料。
Working with iOS
同樣的,Azure Mobile Services SDK也有iOS的版本,請由下列網址下載。
https://go.microsoft.com/fwLink/p/?LinkID=266533
解開後將其中的兩個目錄複製到你的iOS專案並加入,本文是建立一個新的Single View Application。
圖22
修改主UI,加入TableView控制項,並修改ViewController.h。
ViewController.h
//
// ViewController.h
// AMServcieDemo
//
// Created by code6421 on 13/4/29.
// Copyright (c) 2013年 Lion User. All rights reserved.
//
#import
#import
@interface ViewController : UIViewController
@property(strong, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, strong) MSClient *client;
@property (nonatomic, strong) MSTable *table;
@property (nonatomic, strong) NSArray *items;
@end
記得要設定UI中TableView與Outlet間的關聯,完成後修改ViewController.m。
ViewController.m
//
// ViewController.m
// AMServcieDemo
//
// Created by code6421 on 13/4/29.
// Copyright (c) 2013年 Lion User. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.client = [MSClient clientWithApplicationURLString:@"https://myrest.azure-mobile.net/" applicationKey:@"<app key>"];
self.table = [self.client tableWithName:@"Customers"];
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"Name=='code6421'"];
[self.table readWithPredicate:predicate completion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
self.items = [results mutableCopy];
[self.tableView reloadData];
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *tableIdentifier = @"Simple table";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableIdentifier];
}
NSDictionary *item = [self.items objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:@"Name"];
return cell;
}
-(void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
}
@end
一開始建立MSClient物件,然後透過它取得資料表的參考物件,也就是MSTable,接著再透過它來查詢資料。
- (void)viewDidLoad
{
[super viewDidLoad];
self.client = [MSClient clientWithApplicationURLString:@"https://myrest.azure-mobile.net/" applicationKey:@"<app key>"];
self.table = [self.client tableWithName:@"Customers"];
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"Name=='code6421'"];
[self.table readWithPredicate:predicate completion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
self.items = [results mutableCopy];
[self.tableView reloadData];
}];
}
然後顯示於TableView的Cell之上。
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *tableIdentifier = @"Simple table";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableIdentifier];
}
NSDictionary *item = [self.items objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:@"Name"];
return cell;
}
下圖是執行結果。
圖23
新增、修改、刪除等動作則是透過MSTable的insert、update、delete函式,跟Windows Phone、Android不同的是iOS並沒有所謂的Customers類別,而是用NSDictionary來呈現資料列。
NSDictionary *item = @{ @"Name" : “c32”, @"Address" : @”taiwan” };
下列連結是Mobile Services SDK for iOS 的參考文件。
http://dl.windowsazure.com/iosdocs/index.html