SharePoint Framework client-side web パーツ開発のファースト ステップ

SharePoint Framework は、SharePoint の新しいページ/Web パーツ モデルです。Modern UI とか新しい UI エクスペリエンス等とよばれていますが、SharePoint Online では、リスト/ライブラリ/ページの新しい UI の提供が始まっています。

■ 新しいページ概要 (作成・編集)

1.新しいページを作成する場合、[設定] – [ページの追加] をクリックします。

1
2.ページが作成され、編集モードで開きます。

2
3.タイトルをつけて、

3
4.Web パーツの挿入も可能です。

 4.png
5.配置した Web パーツは、設定メニューから Web パーツの編集画面を開き、設定変更が可能です。

5.png

これまでの Web パーツの設定画面とは少し違い、[適用] や [OK] をクリックしなくても、設定内容が適用されることが確認できます。

 

6. [発行] をクリックして、ページを発行します。

6.png


ページ ファイルは [サイトのページ] ライブラリに格納され、設定によりトップページにすることも可能

7.png

 

■ SharePoint Framework 開発ツール

開発ツールについて、詳細は https://dev.office.com/sharepoint/docs/spfx/tools-and-libraries にありますが、下記を利用します。


・ TypeScript
SharePoint Framework で開発するクライアントサイド Web パーツの開発は、TypeScript クラス、モジュール、
インターフェースを利用します。

  ・ Node.js、NPM
SharePoint client-side developmet tools は NPM パッケージ マネージャーを利用しています。

  ・ Gulp
ビルド プロセスに Gulp を利用します。

  ・ Yeoman generators
新しいプロジェクトを作成する際に利用します。

 

■ 開発を行う前に: 開発環境の準備

開発に利用するコード エディターはなにを利用してもいいと思いますが、すでに私の PC にインストールされている Visual Studio Code を利用した手順をご紹介します。

   Visual Studio Code のインストールはこちらから https://code.visualstudio.com/download

開発環境に必要なツールを順番にインストールします。

1.Node.js (Long Term Support (LTS) version)
https://nodejs.org/en/ より、ダウンロードしてインストールします。

2.windows-build-tools

  Node.js をインストールすれば、NPM (Node Package Manager) もインストールされるため、下記コマンドでパッケージ名を指定してインストールします。

npm install -g –production windows-build-tools

   installtool1

3. Yeoman と gulp
Yeoman は node.jsベースで動くスキャフォールディングツールです。下記コマンドでインストールします。

npm install -g yo gulp

4.Yeoman SharePoint Generator

npm install -g @microsoft/generator-sharepoint

■ 開発を行う前に: Office 365 テナントの準備

SharePoint Framework の開発を試す際、Office 365 テナントが必要です。また先行リリースが有効になっているかどうかを確認ください。

・ Office 365 管理センターで、[組織のプロファイル] より先行リリースを有効に設定してください。
  先行リリース

■ 簡単な Client-side Web パーツを作ってみる (指定したリストのアイテム一覧)

さて、では簡単な Client-side Web パーツの開発を行ってみましょう。

 Step 1 プロジェクト作成

   Yeoman でクライアント サイド Web パーツ作成時に必要なフォルダーやファイルを生成させます。

1.Node.js のコマンド プロンプトを起動します。

2.プロジェクト フォルダーを作成したいディレクトリーに移動して、
   dev1

3.新しいプロジェクト フォルダー作成します。

md test-webpart

   dev2

4.作ったプロジェクト フォルダーに移動して、

cd test-webpart

   dev3

5.Yeoman SharePoint Generator で、中身を生成します。

yo @microsoft/sharepoint

  途中で、Web パーツ名や説明、framework 選択が表示されますが、すべて既定値で、[Enter]

   dev4

6.しばらく待つと、プロジェクトが生成されます。
  dev5

   プロジェクトを作成したフォルダー内を確認すると、こんな感じです。
   dev6

Step 2  テスト実行

   プロジェクトの初期状態で、ローカルでテスト実行を行ってみます。

1.HTTPS エンドポイントを利用して実行されるのが既定です。開発環境ローカルに証明書をインストールします。
(すでに行っている場合は、必要ありません)

     Node.js のコマンド プロンプトで次を実行します。

gulp trust-dev-cert

2.次のコマンドで、実行します。

gulp serve

   https://localhost:4321 がブラウザーで起動します。
テストページとして利用できる Workbench /temp/workbench.html が開きます。
  dev7

3.Web パーツを追加してみます。
  dev8

  設定メニューを開き、プロパティに入力した内容が、Web パーツ内に反映されることが確認できます。

     dev9

また Workbench は、テスト実行中、既存の SharePoint サイトに /_layouts/15/workbench.aspx を指定しても開けます。
  dev10

このままテスト実行したまま、ソースコードの内容を確認し、編集を加えていきます。

Step 3  プロジェクト内容 基本確認

プロジェクトを Visual Studio Code で開いてみます。

1. Visual Studio Code を起動し、[ファイル] メニュー – [フォルダーを開く] でプロジェクト フォルダーを指定し、開きます。

2. src/wenparts 内に、基本のファイル群が確認できます。
  dev11

3. HelloWorldWebPart.ts を開いてみます。

基本は Basic ClientWebPart クラスを継承して作成し、合わせて Web パーツ名やプロパティ定義を行う定義ファイルが必要です。サーバーサイドの Web パーツをソリューションで開発する際や、アプリパーツを SharePoint Add-ins で開発するときと似ていますね。

  dev12

      ・ BaseClientSideWebPart クラスを継承しています。
・ render() メソッドに、Web パーツ内の内容が含まれています。
${this.properties.description} 部分は、プロパティの description を参照しています。

4.Web パーツの設定画面で変更が可能なプロパティの定義を確認します。
既定でプロパティ画面には、テキストボックスが1つ表示されています。定義を確認してみましょう。
  dev13

    ・ HelloWorldWebPart.ts ファイル内の頭にある import
      dev14

    ・ HelloWorldWebPart.ts ファイル内 propertyPaneSettings プロパティ
     dev15

    ・ IHelloWorldWebPartProps.ts
     dev16

    ・ マニフェスト HelloWorldWebPart.manifest.json
     dev17

 

Step 4  プロパティを追加してみる

プロパティを追加して、画面に表示してみます。

1.HelloWorldWebPart.ts ファイル内の頭にある import に、次を追加

,PropertyPaneCheckbox, PropertyPaneDropdown, PropertyPaneToggle

   dev18

2. HelloWorldWebPart.ts ファイル内 propertyPaneSettings プロパティを、次のように変更します。

protected get propertyPaneSettings(): IPropertyPaneSettings {
  return {
pages: [
{
header: {
description: “設定してください。”
},
groups: [
{
groupName: “設定グループA”,
groupFields: [
PropertyPaneTextField(‘description’, {
label: “リスト名を設定”
}),
PropertyPaneTextField(‘test1’, {
label: ‘複数行テキスト’,
multiline: true
}),
PropertyPaneCheckbox(‘test2’, {
text: ‘チェックボックス’
}),
PropertyPaneDropdown(‘test3’, {
label: ‘ドロップダウン’,
options: [
{ key: ‘カテゴリーA’, text: ‘カテゴリーA’ },
{ key: ‘カテゴリーB’, text: ‘カテゴリーB’ },
{ key: ‘カテゴリーC’, text: ‘カテゴリーC’ },
{ key: ‘カテゴリーD’, text: ‘カテゴリーD’ }
]}),
PropertyPaneToggle(‘test4’, {
label: ‘トグル’,
onText: ‘On’,offText: ‘Off’
})
]
}]
}]
};
}

    dev19

3. IHelloWorldWebPartProps.ts を次のように変更します。

export interface IHelloWorldWebPartProps {
description: string;
test1: string;
test2: boolean;
test3: string;
test4: boolean;
}

    dev20

4. マニフェスト HelloWorldWebPart.manifest.json 内のプロパティバッグを次のように編集し、既定値を設定します。

“preconfiguredEntries”: [{
“groupId”: “d2f78fea-15d7-474c-8b5e-c56a7825c51f”,
“group”: { “default”: “Under Development” },
  “title”: { “default”: “HelloWorld” },
“description”: { “default”: “HelloWorld description” },
“officeFabricIconFontName”: “Page”,
“properties”: {
“description”: “HelloWorld”,
“test1”: “サンプルテキスト”,
“test2”: true,
“test3”: “カテゴリーA”,
“test4”: true
}
}]

   dev21

5. HelloWorldWebPart.ts ファイル内 render() に次を追加します。

<p class=”ms-font-l ms-fontColor-white”>test : ${this.properties.test1}</p>
<p class=”ms-font-l ms-fontColor-white”>test1 : ${this.properties.test2}</p>
<p class=”ms-font-l ms-fontColor-white”>test2 : ${this.properties.test3}</p>
<p class=”ms-font-l ms-fontColor-white”>test3 : ${this.properties.test4}</p>

  dev22

6.すべて保存し、テスト実行中の画面で確認してみます。

  dev23

 

Step 5  SharePoint リスト アイテムを取得・表示してみる

指定したリストのリスト アイテムを一覧する機能を追加してみます。

1.HelloWorldWebPart.ts ファイル内に、次のインターフェイスを追加します。

export interface ISPLists {
value: ISPList[];
}
export interface ISPList {
Title: string;
Id: string;
Description: string;
}

2. HelloWorldWebPart クラス内に、次のメソッドを追加します。

private _getListData(): Promise<ISPLists> {
return this.context.httpClient.get(this.context.pageContext.web.absoluteUrl + ‘/_api/web/lists/GetByTitle(\”+this.properties.description+’\’)/Items’)
.then((response: Response) => {
return response.json();
});
}
private _renderListAsync(): void {
this._getListData().then((response) => {
this._renderList(response.value);
});
}
private _renderList(items: ISPList[]): void {
let html: string = ‘<table>’;
html += `<th>ID</th><th>Title</th><th>Description</th>`;
items.forEach((item: ISPList) => {
html += `<tr><td>${item.Id}</td><td>${item.Title}</td>
<td>${item.Description}</td></tr>`;
});
html += `</table>`;
const listContainer: Element = this.domElement.querySelector(‘#spListContainer’);
listContainer.innerHTML = html;
}

3.render()を次のように変更します。
render

4. HelloWorld.module.scss に次を追加します。

.listTable table{
border-collapse: collapse;width:100%;margin-top:5px; }
.listTable th{
padding: 6px;  text-align: left;  vertical-align: top;  color: #333;
background-color: #eee;   border: 1px solid #b9b9b9;  }
.listTable td{
padding: 6px;  background-color: #fff;  border: 1px solid #b9b9b9;  }

5.すべて保存し、テスト実行中の画面で結果を確認してみます。
Web パーツ設定画面で指定したリスト名の、リストアイテムを取得して、表示しています。
※ 上記コードでは、同じサイト内の指定したリストに対して、ID、Title、Description 列を取得しています。
※ ローカル環境の HTML での実行では、結果は得られません。SharePoint サイト内
の /_layouts/15/workbench.aspx を利用して結果を確認します。

      dev24

6.テスト実行を止める場合は、Ctrl + S で。

 

 

長くなってきたので、今回はここまでにします。
SharePoint Framework でのクライアント サイド Web パーツの開発を試す参考手順にいただければ幸いです。

クライアント サイド Web パーツ は、SharePoint Add-ins で開発するアプリ パーツと違って、iframe で動作しているわけではありません。そのためカレント ユーザーのコンテキストでブラウザー上で実行されます。ユーザー向けの簡単な機能だったら、コンテキストの受け渡しだのアクセス許可だのめんどくさいことを考えなくてもよい分、SharePoint Add-ins よりも、こちらのほうがよっぽどわかりやすく開発できそう。

また既存の SharePoint ページでの利用も可能だそうです。
既存の SharePoint ページ (従来の UI) で利用できるように展開する方法とか、Office Graph API 使った内容とか、Office UI Fablic  使ってみる内容とかふれてみたいなと思ってます。

SharePoint 開発は、オンプレ環境限定のサーバー サイドコードで拡張を行う 「ソリューション」、SharePoint 外部で動作するように開発する 「SharePoint Add-ins」 と現在2種類の手法があります。
それに 「SharePoint Framework」 が追加されてくることで、どういったときにどの開発方法を利用するか、さらに考慮が悩ましくなってきそうです。SharePoint Framework はまだプレビューですが、変更点等これからも確認していきたい内容です。

SharePoint Visual Studio によるアプリ開発 っていう SharePoint 開発者向けのセミナーを定期的に開催してるのですが、、SharePoint Framework が正式リリースされたら、別セミナーにするか、現行のセミナーに内容を追加するか、悩みます。。受講する方からすると、SharePoint 開発のあれもこれも、1回の受講で理解できたほうがいいはず?

de:code フォローアップ② Office アプリでの SharePoint Online アクセス (Common Consent Framework 利用)

先日 開催された de:code で担当させていただいた Office アプリ開発 SP1 新機能セッションフォローアップ記事の第2弾です。

セッションの一番最後に、デモで動作を見ていただき、処理内容を解説させていただいた、Office 用アプリで、Office 365 API で提供される Common Consent Framework を利用し、SharePoint Online へアクセスする内容のサンプルコード紹介です。コードを作成する際の、詳細手順はフォローアップ記事① でふれていますので、あわせて参考にしていただければと思います。

また、Office 365 の新しい API や Common Consent Framework については、MS 松崎さんのステキ濃ゆい Blog で解説されていますので、そちらをぜひ~。

   ・ 松崎さん Blog 「Office 365 API 入門
                          「Azure Active Directory の Common Consent Framework (Service 側)

■  Azure Active Directory にアプリを登録

   ●  Office 365 テナントのアカウントで、Azure 管理ポータルにサインアップ
        (初回はユーザー情報の登録が必要)

   ●  アプリを登録

       1. [アプリケーション] を開き、[追加] をクリック

           MailApp1

       2. [組織で開発中のアプリケーションを追加]

          MailApp2

       3. 名前を指定し、[Web アプリケーションや Web API] を選択 

       4. サインオン URL、アプリケーション ID にアプリ内のリダイレクト HTML ページの URL を指定

       5. 登録されたら、構成画面で、クライアント ID とキーをひかえておく

         MailApp3

       6. 同じく構成画面で、[他のアプリケーションに対するアクセス許可] でアクセスしたい内容を指定

         MailApp4

コード例 (メールアプリで取得した添付ファイルを、OneDriveに保存するメール表示フォームの例)
    ※ 各ファイルやクラス等の名前は、任意に変更ください。

   ● Manifest でのアクティブ化ルール

        MailApp5

   ● 添付ファイル処理 コントローラークラス (GetAttachmentController クラス)

    // POST 処理
    [HttpPost()]
    public string SaveAttachment(AttachmentRequest request)
    {
      try
      {
        Attachment attachment = GetAttachmentFile(request.AttachmentId,
            request.AuthToken, request.EwsUrl);
        return SaveTOSP(attachment);
      }
      catch (Exception e)
      {
        return "エラー発生: " + e.Message + "\n\n" + e.StackTrace;
      }
    }

    // EWS よびだしを行い、添付ファイルを取得
    private Attachment GetAttachmentFile(string attachmentId,
        string authToken, string ewsUrl)
    {
      // GetAttachment SOAP リクエスト用に文字列作成
      string getAttachmentRequest =
          @"<?xml version=""1.0"" encoding=""utf-8""?>
           <soap:Envelope xmlns:xsi=""
http://www.w3.org/2001/XMLSchema-instance"&quot;
            xmlns:xsd=""http://www.w3.org/2001/XMLSchema"&quot;
            xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"&quot;
            xmlns:t=""http://schemas.microsoft.com/exchange/services/2006/types"&quot;>
                <soap:Header>
                <t:RequestServerVersion Version=""Exchange2013"" />
                </soap:Header>
                    <soap:Body>
           <GetAttachment   
           xmlns="
http://schemas.microsoft.com/exchange/services/2006/messages&quot;
           xmlns:t=""http://schemas.microsoft.com/exchange/services/2006/types"&quot;>
                        <AttachmentShape/>
                        <AttachmentIds>
                        <t:AttachmentId Id=""{0}""/>
                        </AttachmentIds>
                    </GetAttachment>
                    </soap:Body>
                </soap:Envelope>";
      getAttachmentRequest = String.Format(getAttachmentRequest, attachmentId);

      // Web リクエストオブジェクトの作成
      HttpWebRequest webRequest = WebRequest.CreateHttp(ewsUrl);
      webRequest.Headers.Add("Authorization",
          string.Format("Bearer {0}", authToken));
      webRequest.PreAuthenticate = true;
      webRequest.AllowAutoRedirect = false;
      webRequest.Method = "POST";
      webRequest.ContentType = "text/xml; charset=utf-8";
      byte[] bodyBytes
        = System.Text.Encoding.UTF8.GetBytes(getAttachmentRequest);
      webRequest.ContentLength = bodyBytes.Length;

      Stream requestStream = webRequest.GetRequestStream();
      requestStream.Write(bodyBytes, 0, bodyBytes.Length);
      requestStream.Close();

      // EWS リクエスト
      HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();

      // XML ドキュメントをレスポンスから取得
      if (webResponse.StatusCode == HttpStatusCode.OK)
      {
        Stream responseStream = webResponse.GetResponseStream();
        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.Load(responseStream);

        string fileName = xmlDocument.
            GetElementsByTagName("t:Name")[0].InnerText;
        byte[] bytes = Convert.FromBase64String(xmlDocument.
            GetElementsByTagName("t:Content")[0].InnerText);

        // レスポンス クローズ
        responseStream.Close();
        webResponse.Close();

        return new Attachment()
        {
          AttachmentBytes = bytes,
          AttachmentName = fileName
        };
      }
      return null;
    }

    // SharePoint ライブラリにファイル保存
    private string SaveTOSP(Attachment attachment)
    {
      string ResourceId =
https://****-my.sharepoint.com/;
      string ApiEndpoint = https://*****-my.sharepoint.com/personal/***/_api;
      string accessToken = OAuthController.GetAccessToken(ResourceId);

      // HTTP リクエスト (新しい File API 利用)
      HttpWebRequest webRequest = WebRequest.CreateHttp(ApiEndpoint
          + "/files/Add(name=’" + attachment.AttachmentName
          + "’, overwrite=true)");
      webRequest.Accept = "application/json;odata=verbose";
      webRequest.Headers.Add("Authorization",
          string.Format("Bearer {0}", accessToken));
      webRequest.Method = "POST";
      webRequest.ContentLength = attachment.AttachmentBytes.Length;
      webRequest.ContentType = "application/octet-stream";

      Stream requestStream = webRequest.GetRequestStream();
      requestStream.Write(attachment.AttachmentBytes, 0,
          attachment.AttachmentBytes.Length);
      requestStream.Close();

      // SharePoint へリクエスト送信
      HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
      if (webResponse.StatusCode == HttpStatusCode.OK)
      {
        Stream responseStream = webResponse.GetResponseStream();
        StreamReader reader = new StreamReader(responseStream);
        return reader.ReadToEnd();
      }
      return "エラー";
    }

    public class Attachment
    {
      public byte[] AttachmentBytes { get; set; }
      public string AttachmentName { get; set; }
    }

    public class AttachmentRequest
    {
      public string AuthToken { get; set; }
      public string AttachmentId { get; set; }
      public string EwsUrl { get; set; }
    }

   ● OAuthController クラス内容

     // AAD のクライアントID 指定
    private static readonly string ClientId = "クライアントID";
    // AAD のクライアントシークレット指定
    private static readonly string ClientSecret = "キー";
    private const string OAuthUrl = "
https://login.windows.net/{0}";
    private static readonly string AuthorizeUrl
      = string.Format(CultureInfo.InvariantCulture, OAuthUrl,
        "common/oauth2/authorize?response_type=code&client_id={0}&resource={1} 
        &redirect_uri={2}");
    private static readonly Uri RedirectUrl = new Uri(
        System.Web.HttpContext.Current.Request.Url,"/リダイレクトページ.html");

    [HttpPost()]
    public string GetAuthorizationUrl(AuthorizationRequest request)
    {
      return String.Format(CultureInfo.InvariantCulture, AuthorizeUrl,
          Uri.EscapeDataString(ClientId), Uri.EscapeDataString(request.ResourceId),
          Uri.EscapeDataString(RedirectUrl.ToString())
      );
    }

    [HttpPost()]
    public string OAuthFlow(AuthorizationParameters parameters)
    {
      try
      {
        ClientCredential credential = new ClientCredential(ClientId, ClientSecret);
        string authority =
          string.Format(CultureInfo.InvariantCulture, OAuthUrl, "common");
        AuthenticationContext authContext = new AuthenticationContext(authority);
        AuthenticationResult result
          = authContext.AcquireTokenByAuthorizationCode(parameters.Code,
            new Uri(RedirectUrl.GetLeftPart(UriPartial.Path)),credential);
        // トークンのキャッシュ
        SSInfo.RefreshToken = result.RefreshToken;
        return "OAuth 認証終了";
      }
      catch (ActiveDirectoryAuthenticationException ex)
      {
        return "OAuth 失敗" + ex.ToString();
      }
    }

    public static string GetAccessToken(string resourceId)
    {
      try
      {
        string refreshToken = Storage.SSInfo.RefreshToken;
        ClientCredential credential = new ClientCredential(ClientId, ClientSecret);
        string authority =
          string.Format(CultureInfo.InvariantCulture, OAuthUrl, "common");
        AuthenticationContext authContext = new AuthenticationContext(authority);
        AuthenticationResult result = authContext.AcquireTokenByRefreshToken(
            refreshToken, ClientId, credential, resourceId);
        return result.AccessToken;
      }
      catch (ActiveDirectoryAuthenticationException)
      {
        return null;
      }
    }

    public class AuthorizationRequest
    {
      public string ResourceId { get; set; }
    }
    public class AuthorizationParameters
    {
      public string Code { get; set; }
    }
   public class SSInfo
    {
      public static string RefreshToken { get; set; }
      public static string ClientId { get { return ""; } }
      public static string ClientSecret { get { return ""; } }
    }

   ● リダイレクト HTML ページの内容

    
       app.initialize();
       var oauthToken = {
           Code : getUrlParameter(‘code’)
       };

       $.ajax({
           url: ‘../../api/OAuth/OAuthFlow’,
           type: ‘POST’,
           data: JSON.stringify(oauthToken),
           contentType: ‘application/json;charset=utf-8’
       }).done(function (data) {
           app.showNotification(JSON.stringify(data));
           window.close();
       }).fail(function (status) {
           app.showNotification(‘エラー’, JSON.stringify(status));
       });

       function getUrlParameter(parameterName) {
           var pattern = “[\\?&]” + parameterName + “=([^&#]*)”,
               regularExpression = new RegExp(pattern),
               results = regularExpression.exec(window.location.href);

           return results ? results[1] : null;
       }
  

   ● アプリの HTML

      <button id="providePermission">① アクセス許可取得</button>
      <button id="saveToSP">② 添付ファイルを OneDrive へ保存 </button>

   ● アプリの HTML から参照する js

      – Initialize

          $(‘#providePermission’).click(To365OAuthFlow); 
          $(‘#saveToSP’).click(saveToSP);

     
      – 関数追加

    // OAuth フローのはじまり
     function To365OAuthFlow() {
        var dataToSend = {
            ResourceId: "Microsoft.SharePoint"
        }

        $.ajax({
            url: ‘../../api/OAuth/GetAuthorizationUrl’,
            type: ‘POST’,
            data: JSON.stringify(dataToSend),
            contentType: ‘application/json;charset=utf-8’
        }).done(function (data) {
            window.open(data);
        }).fail(function (status) {
            app.showNotification(‘エラー’, JSON.stringify(status));
        }).always(function () {
            $(‘.disable-while-sending’).prop(‘disabled’, false);
        });
    }

    // OneDrive へ保存
    function saveToSP() {
        var attachmentId = Office.context.mailbox.item.attachments[0].id;
        var ewsUrl = Office.context.mailbox.ewsUrl;
        Office.context.mailbox.getCallbackTokenAsync(function (ar) {
            var attachmentData = {
                AuthToken: ar.value,
                AttachmentId: attachmentId,
                EwsUrl: ewsUrl
            };

            sendRequest("GetAttachment/SaveAttachment", attachmentData);
        });
    }
   
  
    // ヘルパー関数 コントローラークラス内の API 呼び出し
     function sendRequest(method, data) {
        $.ajax({
            url: ‘../../api/’ + method,
            type: ‘POST’,
            data: JSON.stringify(data),
            contentType: ‘application/json;charset=utf-8’
        }).done(function (data) {
            app.showNotification("成功", "");
        }).fail(function (status) {
            app.showNotification(‘エラー’, JSON.stringify(status));      
        });
     }  

 

以上、コード内で行っている処理はセッション中に解説させていただいたとおりです。

上記サンプルでは、OneDrive へファイルを Add する REST コールを行っていますが、同様に、サンプルコード内の OAuthController.GetAccessToken で取得できるトークンをリクエストヘッダーで利用し、365 コンテンツへの REST アクセスが行っていただけます。

以上、参考にしていただければと思います。

奥田 うさぎ

de:code フォローアップ① Outlook メールアプリ での添付ファイル操作

こんにちは、奥田です。先日 de:code で Office アプリ開発 SP1 新機能についてのセッションを担当させていただきましたが、そのフォローアップ記事第1弾です。

少し前にリリースされた Office 2013 SP1 にて、Office 用アプリ開発においても新機能がたくさんあります。セッションでは、特に注目すべき変更点として Outlook のメールアプリについて、メインにお話させていただきました。

この記事では、その中で解説させていただいた添付ファイル操作について、サンプルコードの作成方法を補足させていただきます。

添付ファイル操作は、1.1 で追加された新しい API の利用で、ReadItem (項目の読み取り) 以上のアクセス許可レベルで可能となり、また OWA だけではなく、Outlook クライアントでもサポートされるようになりました。

参考:方法: Exchange サーバーから Outlook アイテムの添付ファイルを取得する

上記記事にあるサンプルコードを参考に、添付ファイルを操作するメールアプリを作成する際の参考手順です。
サーバーサイドコードの部分は、API コントローラーにより REST サービスとして用意したいと思います。

  ※  JSAPI 1.1 機能を利用するため、Microsoft Office Developer Tools for Visual Studio 2013 -  
       March 2014 Update パッケージ をインストールした環境で試していただけます。

1.VS でメールアプリプロジェクトを作成
     ●
メールの表示フォーム
     ●
[項目の読み取り] アクセス許可 (Manifest で設定)
     ●
メールボックスの要件セット 1.1 (Manifest で設定)

2.Web API コントローラー実装
     ●
[Webプロジェクト] を右クリック – [追加] – [Web API コントローラー クラス] を選択
         ※
メニューがでてこないときは、NuGet Microsoft ASP.NET Web API パッケージを入れるとよいかと

     ● クラス名を [***Controller] とします。
         ※ 
このあと、ASP.NET ルーティングでURLを構成するため、クラス名のおしりに、"Controller"
             とつけときます。

     ● コントローラークラス内の既定のコードを全部消して、次のコードを記述

    // Post アクションメソッド
    [HttpPost()]
    public string GetAttachment(AttachmentRequest request)
    {
      try
      {
        // EWS よびだしで、添付ファイル取得
        Attachment attachment = GetAttachmentFile
            (request.AttachmentId, request.AuthToken, request.EwsUrl);
        // 取得した添付ファイルをローカル保存 (パスは任意に変更を)
        System.IO.File.WriteAllBytes(@"c:\shared\"
            + attachment.AttachmentName, attachment.AttachmentBytes);
      }
      catch (Exception e)
      {
        return "エラー発生: " + e.Message + "\n\n" + e.StackTrace;
      }
      return "保存完了しました!";
    }

    // EWS よびだしを行い、添付ファイルを取得
    private Attachment GetAttachmentFile(string attachmentId,
        string authToken, string ewsUrl)
    {
      // GetAttachment SOAP リクエスト用に文字列作成
      string getAttachmentRequest =
          @"<?xml version=""1.0"" encoding=""utf-8""?>
              <soap:Envelope xmlns:xsi=""
http://www.w3.org/2001/XMLSchema-instance""
                xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
                xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/
              xmlns:t=""http://schemas.microsoft.com/exchange/services/2006/types"">
                <soap:Header>
                <t:RequestServerVersion Version=""Exchange2013"" />
                </soap:Header>
                    <soap:Body>
                    <GetAttachment
              xmlns=""
http://schemas.microsoft.com/exchange/services/2006/messages""
              xmlns:t=""http://schemas.microsoft.com/exchange/services/2006/types"">
                        <AttachmentShape/>
                        <AttachmentIds>
                        <t:AttachmentId Id=""{0}""/>
                        </AttachmentIds>
                    </GetAttachment>
                    </soap:Body>
                </soap:Envelope>";

      getAttachmentRequest = String.Format(getAttachmentRequest, attachmentId);

      // Web リクエストオブジェクトの作成
      HttpWebRequest webRequest = WebRequest.CreateHttp(ewsUrl);
      webRequest.Headers.Add("Authorization",
                        string.Format("Bearer {0}", authToken));
      webRequest.PreAuthenticate = true;
      webRequest.AllowAutoRedirect = false;
      webRequest.Method = "POST";
      webRequest.ContentType = "text/xml; charset=utf-8";
      byte[] bodyBytes
        = System.Text.Encoding.UTF8.GetBytes(getAttachmentRequest);
      webRequest.ContentLength = bodyBytes.Length;

      Stream requestStream = webRequest.GetRequestStream();
      requestStream.Write(bodyBytes, 0, bodyBytes.Length);
      requestStream.Close();

      // EWS リクエスト
      HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();

      // XML ドキュメントをレスポンスから取得
      if (webResponse.StatusCode == HttpStatusCode.OK)
      {
        Stream responseStream = webResponse.GetResponseStream();

        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.Load(responseStream);

        string fileName = xmlDocument.
            GetElementsByTagName("t:Name")[0].InnerText;
        byte[] bytes = Convert.FromBase64String(xmlDocument.
            GetElementsByTagName("t:Content")[0].InnerText);

        // レスポンス クローズ
        responseStream.Close();
        webResponse.Close();

        return new Attachment()
        {
          AttachmentBytes = bytes,
          AttachmentName = fileName
        };
      }
      return null;
    }

    public class Attachment
    {
      public byte[] AttachmentBytes { get; set; }
      public string AttachmentName { get; set; }
    }

    public class AttachmentRequest
    {
      public string AuthToken { get; set; }
      public string AttachmentId { get; set; }
      public string EwsUrl { get; set; }
    }

3.Global.asaxを作成し、API がよびだされるようルーティング規則を指定
    ●
[Webプロジェクト] を右クリック – [追加] – [グローバルアプリケーション クラス] を選択
         名前は Global のままにする

    ● Application_Start メソッド内に、次のコードを記述

          RouteTable.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

        (System.Web.Http と System.Web.Routing を using 追加)

4. メールアプリの表示フォームとして利用する html ファイルに、下記ソースを追加

      <button id="getAttachment">添付ファイル保存</button>

5.参照する js ファイルに、下記ソースを追加
     ●
Initialize メソッド内に、クリック操作追加

         $(‘#getAttachment’).click(getAttachment);

     ● 次のメソッド追加

   // 添付ファイル保存
    function getAttachment() {
        // 添付ファイル ID を JSAPI 利用して取得
        var attachmentId = Office.context.mailbox.item.attachments[0].id;
        // EWS EndPoint URL を JSAPI 利用して取得
        var ewsUrl = Office.context.mailbox.ewsUrl;
        // コールバックトークンを取得
        Office.context.mailbox.getCallbackTokenAsync(function (ar) {
            var attachmentData = {
                AuthToken: ar.value,
                AttachmentId: attachmentId,
                EwsUrl: ewsUrl
            };
            // コントローラークラス (サーバーサイドコード) の
            // GetAttachment を呼び出し (添付ファイルID、EWS URL、Token を渡す)
            sendRequest("コントローラー名/GetAttachment", attachmentData);
        });
    }

    // ヘルパー関数 コントローラークラス内の API 呼び出し
     function sendRequest(method, data) {
        $.ajax({
            url: ‘../../api/’ + method,
            type: ‘POST’,
            data: JSON.stringify(data),
            contentType: ‘application/json;charset=utf-8’
        }).done(function (data) {
            app.showNotification("成功", "指定の場所に保存しました。");
        }).fail(function (status) {
            app.showNotification(‘エラー’, JSON.stringify(status));
        });
     }

以上、コード内で行っている処理はセッション中に解説させていただいたとおりです。
参考にしていただければと思います。

奥田  うさぎ

10/1~4 開催 SharePoint 2013 開発者向け某トレーニングにご参加頂いた皆様へ

こんにちは、奥田です。

今週 10/1 ~ 4 に開催させていただいた、SharePoint 2013 Preview 開発者向けハンズオン トレーニングにご参加頂いた皆様、お忙しい中4日間のご参加ありがとうございました。
(渋谷のイベントよりも優先いただいた方もおられ、ありがとうございました)

ご案内させていただいたとおり、下記 DL できるようにしました。ご利用くださいませ。
IgniteDev2013inOctOkudaDemo.zip

・ Demo でご紹介した内容のサンプル ソースコード
(App for SharePoint の PermissionRequest と AutoHosted と CrossDomain のやつです)
・ 前で表示させていただきながら書いたメモファイル

パスワードはお伝えしたとおりです。

  以上、ご案内でした。 奥田理恵 うさぎ

Windows Identity Foundation VS に [STS 参照の追加] がでてこない・・

Windows Identity Foundation 3.5 SDK をインストールしたのに、VS 2010 に [STS 参照の追加] がでてこないときの解決方法です。
  sts1

うちの中原さんに教えてもらったので、ブログでも共有したいと思います。

  1. VS 2010 で、[ツール] メニュー – [オプション] を開く
  2. [環境] – [アドイン/マクロ セキュリティ] で [追加] をクリック
  3. C:\Program Files(x86)\Windows Identity Foundation SDK\v3.5\Visual Studio Extensions を追加
  4. VS 再起動

以上、今日は開発環境トラブルネタでした。

奥田理恵

InfoPath 2010 VSTA で SharePoint オブジェクトの利用

InfoPath 2010 の VSTA で、SPWeb とか SPList 等の Microsoft.SharePoint.dll を利用したコードを記述した場合、コースのセキュリティを完全信頼にしないと実行時エラーとなります。

「System.Security.SecurityException:型 ‘microsoft.sharepoint.security.sharepointpermission, microsoft.sharepoint.security, version=14.0.0.0, culture=neutral, publickeytoken=71e9bce111e9429c’ のアクセス許可の要求に失敗しました。」 とエラーとなります。

SharePoint オブジェクトを扱った InfoPath フォーム開発を行う場合は、完全信頼が必須です。
また完全信頼とすることで、発行時には管理者発行を行います。
(発行ウィザードで [管理者発行] 以外は選択できないようになるはずです)。

以上、InfoPath フォーム開発時の注意点でした。

奥田理恵

SharePoint 2010 カスタム WCF サービスの展開

こんにちは、奥田です。
かなーり以前に、「InfoPath Forms Services 匿名アクセスで投稿したい!」 というタイトルで、InfoPath Forms Services のブラウザー フォームを匿名アクセス権限でライブラリに保存するための WCF サービスの作成、公開方法をこのブログに投稿しました。SharePoint Server 2007、InfoPath 2007 のバージョンです。
昨日 SharePoint Server 2010 で同様の WCF サービスを開発したのですが、展開時にハマったポイントがあるので、ご紹介します。

以前の投稿では、WCF サービスを開発して、SharePoint サイトをホストしている IIS 上にアプリケーションとして展開する手順をご紹介しましたが、SharePoint 2010 の場合、この方法では SPWeb とか SPSite とか Microsoft.SharePoint.dll のオブジェクトを利用する際に実行時エラーとなります。
「Microsoft SharePoint は、バージョン 4.0.30319.1 の Microsoft .Net Runtime ではサポートされません。」 と PlatformNotSupportedException がでます。

SharePoint 2010 の場合、WCF サービスを展開する際には、SharePoint 上にカスタム WCF サービスとして展開しなければいけないようです。
カスタム WCF サービスの作成については、下記記事が参考となりますが、展開方法があまり詳しく書かれていなかったので、カスタム WCF サービスを展開する SharePoint プロジェクトの開発手順をご紹介したいと思います。

「MSDN :SharePoint Foundation 2010 の WCF サービス」
http://msdn.microsoft.com/ja-jp/library/ff521586.aspx
「MSDN : カスタム WCF サービスを SharePoint Foundation で作成する」
http://msdn.microsoft.com/ja-jp/library/ff521581.aspx

<手順>

1. Visual Studio 2010 で [空の SharePoint プロジェクト] を作成します。
(ISAPI 以下に WCF サービスを展開するため、ファーム ソリューションで)

2. プロジェクト内にサービス クラスファイルと、サービスコントラクトファイルを追加
   サービスクラスに [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]  を利用して ASP.NET 互換モードを許可するよう構成すること。

3. プロジェクト内に [SharePoint のマップされたフォルダー] で [ISAPI] フォルダーへのマップフォルダーを作成

4. [ISAPI] マップフォルダー内に、さらにフォルダーを作成し、その中に ***.svc と web.config ファイルを作成します。
   ipwcf1

5. ***.svc と web.config を開発した WCF サービスに応じて内容を編集します。
aspNetCompatibilityEnabled は true とすること。

  (例)

   ・ ***.svc
<%@ Assembly Name=”InfoPathWcf,Version=1.0.0.0,Culture=Neutral,PublicKeyToken=4d22f097b6564138″ %>
<%@ ServiceHost Service=”InfoPathWcf.InfoPathSv” %>
 
   ・ web.config

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  
<system.web> </system.web>
 
<system.serviceModel>
 
<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />
 
<services>
  
<service behaviorConfiguration=”InfoPathWcf.InfoPathSvBehavior” name=”InfoPathWcf.InfoPathSv”>
  
<endpoint address=”” binding=”basicHttpBinding” contract=”InfoPathWcf.IInfoPathSv”>
 
    <identity><dns value=”localhost” /></identity>
  
</endpoint>
  
<endpoint address=”mex” binding=”mexHttpBinding” contract=”IMetadataExchange” />
 
<host>
  
<baseAddresses>
    
<add baseAddress=”http://ci:8888/&#8221; />
  
</baseAddresses>
 
</host>
 
</service>
 
</services>
 
<behaviors>
  
<serviceBehaviors>
  
<behavior name=”InfoPathWcf.InfoPathSvBehavior”>
   
<serviceDebug includeExceptionDetailInFaults=”true”/>
   
<serviceMetadata httpGetEnabled=”true” />
  
</behavior>
 
</serviceBehaviors>
 
</behaviors>
 
</system.serviceModel>
 
</configuration>

あとは配置なり、パッケージなりを行います。

展開すると、http://SharePoint URL/_vti_bin/(ISAPI 下のフォルダー名)/****.svc でアクセスできます。
 ipwcf2

  ※ 下記ブログを参考にさせていただきました。
    http://answers.oreilly.com/topic/1404-how-to-customize-wcf-services-in-sharepoint-2010/

以上、奥田でした。