Introducing Windows Azure Mobile Services

從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