Microsoft GraphAPI(formerly “Office 365 unified API”) されたのでアプリをAzureWeb SiteにUPしました。

ついに、Microsoft GraphAPI(formerly “Office 365 unified API”) がGAされましたので、

いまのところがREST APIがメインなのでC#にて作成してみました。

Microsoft GraphAPI(formerly “Office 365 unified API”) GAのニュースは以下から

Introducing the Microsoft Graph –The Azure AD GraphAPI goes big time

http://blogs.technet.com/b/ad/archive/2015/11/19/introducing-the-microsoft-graph-the-azure-ad-graphapi-goes-big-time.aspx

作成したアプリ概要

Microsoft GraphAPI(formerly “Office 365 unified API”)  のV1.0リリース版を使用して、グループとユーザを取得してExcelにてグループとユーザマトリックスを作成しました。

作成したアプリの場所

https://o365info.azurewebsites.net

作成したアプリの画面イメージ

ログイン後

Previe0365list_display1

実行画面

Previe0365list_display2

ダウンロードしたExcelを表示

Previe0365list_display3

 

作成したアプリの作成で参考になったサイトを集めたところ

https://www.facebook.com/groups/office365developer/

 

デモアプリ「Office365 Unified API」+「AzureWebサイト」+「EXCEL Format」にてGroup/User Matrixを作成しました。

概要

「ユーザがどこの配布グループ・セキュリティグループに属しているの?」とよく聞かれます。

実際には、システム管理者が、PowerShell等を使用してマニュアルでエクセルで作成している管理者も多いと思います。

Office365 Unified API(Beta)を使用して、Group/UserをOffice365から取得して、Excelでマトリックスにしてみました。

とても扱いやすい関数ですので、みなさんも作ってみましょう!!

デモサイトは以下です。

注意)ユーザ100件以上のユーザは時間がかかるかもしれません。

https://o365info.azurewebsites.net/

Office365Info010

 

Download Excel ボタンをクリックします。

以下のようにExcelフォーマットでダウンロードされます。

別のシートにグループ一覧、ユーザ一覧も出力しています。

Office365Info020

 

 

プログラムについて以下のサイトが参考になります。

Office 365 unified api を使いこなそう

Office 365 unified API (PREVIEW)

Examples of Office 365 unified API calls (preview)

とりあえず、動かしてみたいなら。

https://apisandbox.msdn.microsoft.com/

 

プログラムの詳細は時間のあるときに書きます。

内部ではUnified APIを使用して、AccessToken一つで Tenant情報/Group一覧/Groupメンバー/User一覧/Userプロフィールを取得しています。

Office365 Unified API便利ですので使ってみてください。

Visual Studio 2013 Update2、ユーザIDがメールアドレスになり、多要素認証が登場

ユーザに「アカウントが登録されました」や「パスワードリセット時」に重要なメールアドレスなのですが

前のVisual Studio ASP.NET MVCで作成すると、メールアドレスがログインアカウントでなかったので、別でメールアドレスを登録させるところを作ったり、手間だったのですが、なんと、Visual Studio 2013 Update2から、ログインIDがメールアドレスになりました!!

また、多要素認証の項目がでててきた。涙~うれしいー

開発者が困っているところは、着々と改善されますね。

 

詳しくはMicrosoft Virtual Academyをみるとよくわかります。

最新の Visual Studio & .NET によるモダン Web アプリケーション開発 ASP.NET Identity

 

画面

電子メールアドレスが、ユーザのログインIDに!これで、メールアドレス必須にできるので、パスワードリセットや通知等に使える!!

ASPNETMVC50010

 

ASP.NETのテーブル(AspNetUsers)

TwoFactorEnabledという多要素認証の項目が!

ASPNETMVC50020

 

Knockout.js でTodoアプリ (Visual Studio2013)

概要

Todoアプリのハンズオンを行うための資料です。

knockout-todo


/Views/Home/ index.cshtml

@{
    ViewBag.Title = "Knockout_Todos";
}

<h2>knockout - Todo</h2>
<div class="container">
    <div class="col-lg-12">
        <div class="input-group">
            <input type="text" class="form-control" data-bind="value: title"/>
            <span class="input-group-btn">
                <button class="btn btn-info" data-bind="click: addTask">Add</button>
            </span>
        </div>
    </div>

    <div class="col-lg-12"><h3>Task List</h3></div>

    <div class="col-lg-12">
        <ul class="list-group" data-bind="foreach: tasks">
            <li class="list-group-item">
                <span data-bind="text: title"></span>
                <a href="#" data-bind="click: $parent.deleteTask" class="pull-right">
                    delete
                </a>
            </li>
        </ul>
    </div>
</div>

@section scripts{
    <script type="text/javascript" src="~/Scripts/knockout-3.1.0.js"></script>
    <script type="text/javascript" src="~/Scripts/test.js"></script>
}

/Scripts/ test.js

$(function () {
    function Task(title) {
        this.title = title;
    }

    function ViewModel() {
        var self = this;
        self.title = ko.observable("");

        self.tasks = ko.observableArray([]); //配列の変更を検知する
        self.addTask = function () {
            var task = new Task(self.title());
            self.tasks.push(task);
            self.title("");
        };

        self.deleteTask = function (task) {
            self.tasks.remove(task);
        }
    }
    ko.applyBindings(new ViewModel());
});

参考

Knockout.js で足し算アプリ (Visual Studio2013)

概要

足し算アプリのハンズオンを行うための資料です。

knockout-caluculate


/Views/Home/ index.cshtml

@{
    ViewBag.Title = "Knockout_Caluculate";
}

<h2>knockout - Caluculate</h2>
<div class="container">
    <p>
            <input data-bind="value:value1" type="text" />
            +
            <input data-bind="value:value2" type="text" />
            =
            <span data-bind="text:result"></span>
    </p>
</div>

@section scripts{
    <script type="text/javascript" src="~/Scripts/knockout-3.1.0.js"></script>
    <script type="text/javascript" src="~/Scripts/test.js"></script>
}

/Scripts/ test.js

$(function () {

    //Modelを定義
    function ViewModel() {
        var self = this;
        self.value1 = ko.observable(0);
        self.value2 = ko.observable(0);
        self.result = ko.computed(function () {
            return parseInt(self.value1()) + parseInt(self.value2());
        });
    }

    //binding (View - VIewMode)
    ko.applyBindings(new ViewModel());
});

参考

Windows Azure のWebサイトでExcelを操作してダウンロード実験してみました。(Visaul Studio 2013 ASP.NET MVC5 + SpreadSheetGear)

概要

有料のExcel操作ツールでのSpreadSheetGear で Windows Azure Webサイトで実験してみました。

このツールのすばらしいところは、10万ちょいの開発ライセンスを買えばWebサーバでも使えること。(一部制限事項あり詳細は以下のサイトから)

http://www.xlsoft.com/jp/products/spreadsheetgear/

実験結果

Windows Azure Webサイトの環境にて、ASP.NET MVC5 (C#)にてサーバサイドで Excelを操作してダウンロード

SpreadSheetGear_0010

Excelファイルをダウンロードの結果

C#のプログラム通りにExcelが編集さされています。

SpreadSheetGear_0020

環境

  • Windows Azure Webサイト(無料)
  • Visual Studio 2013 Professional
    • ASP.NET MVC5
    • .NET Framework 4.5.1
  • SpreadSheetGear 2010

プログラムソース

Test.cshtml

@Html.ActionLink("Download", "Download", new { id = 1 })

@section scripts{
 @Html.Partial("~/views/Users/_Appli-a-ScriptsInclude-0.1.2.cshtml")
 }

HomeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Appli_AWeb_MVC_Main.Controllers
{
public class HomeController : BaseController
{

public ActionResult Test()

{

ViewBag.ActiveCSS_Home_Test = CLASS_ACTIVE;
return View();
}
public FileResult Download(string id)
{
// Create a new empty workbook set.
SpreadsheetGear.IWorkbookSet workbookSet = SpreadsheetGear.Factory.GetWorkbookSet();
// Create a new empty workbook in the workbook set.
SpreadsheetGear.IWorkbook workbook = workbookSet.Workbooks.Add();
// Add a second worksheet.
workbook.Worksheets.Add();
// Rename worksheet "Sheet1" to "Hello"

var sheet1 = workbook.Worksheets["Sheet1"];
sheet1.Name = "Hello";
sheet1.Cells[0, 0].Value = "Test1";
sheet1.Select();
//sheet1.Cells[1, 1].Activate();
//workbook.ActiveWorksheet
var strFileName = Server.MapPath("~/App_Data/Test.xlsx");
workbook.SaveAs(strFileName, SpreadsheetGear.FileFormat.OpenXMLWorkbook);
workbookSet.Workbooks.Close();

//workbook = workbookSet.Workbooks.Open(sheet1);

return File(strFileName,
"application/vnd.ms-excel",
"Test.xlsx");

}

}

}

ログインでセッションをつかうのか? for ASP.NET MVC5

最初に

ログオンの状態とかサーバのセッション変数を実現するにはASP.NETやPHP等でも、セッションIDをクッキーに保存するか、セッションIDをURL文字列に書くのは昔から変わっていないようですが。

最新のASP.NET MVC5でももう一度、確認のため実験してみました。

自分のもってたiPhone3とかはたしかDefaultでCookie 読み込まないしどうしたものか?

でも、Cookie非対応のモバイルブラウザーはGoogleさんも非対応といっているぐらいだからいいか!

Google

Google のユーザー名とパスワードを入力すると、モバイル ブラウザに認証トークンが届きます。これにより、ユーザーは Gmail、Google カレンダー、その他の Google サービスにそのつどログインしなくてもアクセスすることができます。通常、認証トークンは Cookie として保存されます。一部の古いモバイル ブラウザでは Cookie がサポートされていないので、Google で従来の認証メカニズムを一時的に保持していましたが、新しい機能の開発に力を注ぐために、この従来のメカニズムを終了することになりました。

豆知識

HTTP.HeaderのBasic認証の項目を利用して「Cookie」「URLストリングキー」の回避する方法もあります。

詳しくないですが、もう一つの方法としては、HTTP.HeaderのBasic認証の項目を使ってキーをやり取りする方法があるようです、都度XHTTPRequestのたびにJavaScriptでHeaderのBasic認証の項目にキーを追加して、サーバでそのキーを確認する。

セッション(Session)と クッキー(Cookie)についての基礎学習は以下が参考になります。

基本的な実験(Cookie Session)

Visual Studio2013+MVC5 でプロジェクトを作成する。

ASPNETMVC5_Session0010

Web.config にセッション使用しますとかきます。(session)

<configuration>
  <system.web>
    <sessionState mode="InProc" timeout="60"></sessionState>
  </system.web>
</configuration>

HomeController.cs でセッション使う

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
 using System.Web.Mvc;

namespace WebApplication2.Controllers
 {
 public class HomeController : Controller
 {
 public ActionResult Index()
 {
 Session["test"] = 1;
 return View();
 }

public ActionResult About()
 {
 ViewBag.Message = "Your application description page.";

return View();
 }

public ActionResult Contact()
 {
 ViewBag.Message = "Your contact page.";

return View();
 }
 }
 }

実行してFidderで監視します。 ブラウザー側からWebサーバへのRequest でCookieeを使ってセッションIDを送り込んでいます。これでサーバはこのセッションID(SessionID)からサーバ内のセッション(Session)変数を使えるようになります。

ASPNETMVC5_Session0020

Request

Request sent 42 bytes of Cookie data:
ASP.NET_SessionId=amoog5k03kjzueqp2puyhjf0

基本的な実験(Cookieless session)

クッキーレスセッション(cookieless session)にしてみます。

Web.config にクッキーレスセッション使用しますとかきます。(cookieless session)

<configuration>
  <system.web>
    <sessionState mode="InProc" timeout="60" cookieless="true"></sessionState>
  </system.web>
</configuration>

そうすると、Cookie をつかわずに、セッションID(session)をURLでわたします。
それで、Session変数をサーバ側で参照できるようになります。

http://localhost:49506/(S(snioge0aumu1souuueuane5n))/
ASPNETMVC5_Session0030

ログオンの実験(ASP.NET MVC ログオンの実験)

ブラウザーで、クッキーうけいれないように設定してから、ASP.NET MVC5 をデバック実行する。

ASPNETMVC5_Session0060

やはり、クッキーうけいれないようにしたらエラーがでた。

Fiddler でみるといかのクッキーをリクエストしていた。

ASP.NET MVC5 の標準のログインはクッキーは必要ですね。

Request sent 634 bytes of Cookie data:

ASP.NET_SessionId=mpf5oa5sq2x4krnwp2x1kasd; __RequestVerificationToken=xXXc5ZqoikiPcPYr509GbN_AlowCcADWBtufwx_dStVo1zAO2jg_Yce0daLANcNih-gKtTfMqDcdJpe8AR4qKzaYUbGAS6OYmzPsQGU6OO81; .AspNet.ApplicationCookie=7kMtLtGm2zyXqsyHA1HGB1d59U18FormsU17_PGi7AiIbppv-VpTBvyw25VObzi8plEOfPF1deXzJPIl56bvtpyX-oOzjYvuXheQNS06hco_cH506pQPANnl5R7Bv5K5P98UmUrMgY-_Cgg8bxK0SlO8N096pURbWPAAyTl4AH4hrw6crgbCITcl4l3k9AG4dfzDVoDwFYFTnQBEU1NcF_YlMjkbTekEAk0eWgFariBbYVCgmMexT2EJth0IraFo-2zUfZjqX_Ht2yAe8Tdp65y7zzasci_uHeqGfLjcujr_eB2gSTgkqCsq3j-M9JBip645ARStBy9dYqGRPL4eGIVnEOMpOE3_A3xuZyXkXcJ5r1tPEmVAJ6-66CI47WXe6hAFt2ugGlbY1ygXT793kN4U6s_Z7qVmFndB3-lrnBU

Windows8 Phone(Nokia) のTwitterbootstrap の対応方法

通常はこのタグがあれば、PC/Iphone/Android 対応してくれますが。

<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>

なぜかWindows8 Phone だけ対応してくれない。

PCの画面が縮小された感じになる。

twitterbootsrap_Windows8Phone_0010

これにJavaScriptでおまじないをいれるとAndroid/IphoneのようにWindows8Phoneも対応してくれます。

twitterbootsrap_Windows8Phone_0020


 以下をHTMLのHeadに追加する。

<script type=”text/javascript” src=”/Scripts/Users/bootstrap-fix-winphone8.js”>

bootstrap-fix-winphone8.js 

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 <meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>@ViewBag.Title - マイ ASP.NET アプリケーション</title>
 @Styles.Render("~/Content/css")
 @Scripts.Render("~/bundles/modernizr")

@RenderSection("scriptsHead", required: false)

<script type="text/javascript" src="/Scripts/knockout-3.0.0.js"></script>
 <script type="text/javascript" src="/Scripts/Users/bootstrap-fix-winphone8.js"></script>
</head>
<body>
 <div class="navbar navbar-inverse navbar-fixed-top">
 <div class="container">
 <div class="navbar-header">
 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 </button>
 @Html.ActionLink("アプリケーション名", "Index", "Home", null, new { @class = "navbar-brand" })
 </div>
 <div class="navbar-collapse collapse">
 <ul class="nav navbar-nav">
 @*<li>@Html.ActionLink("Home", "Index", "Home")</li>*@
 <li>@Html.ActionLink("BB_1", "Todos", "Home")</li>
 <li>@Html.ActionLink("BB_2", "Todos_WebAPI", "Home")</li>
 <li>@Html.ActionLink("KO_0", "Knockout_Todos", "Home")</li>
 <li>@Html.ActionLink("KO_1", "Knockout_WebAPI1", "Home")</li>
 <li>@Html.ActionLink("KO_2", "Knockout_WebAPI2", "Home")</li>
 <li>@Html.ActionLink("SPA-010", "SPA010", "Home")</li>
 @*<li>@Html.ActionLink("About", "About", "Home")</li>
 <li>@Html.ActionLink("Contact", "Contact", "Home")</li>*@
 </ul>
 </div>
 </div>
 </div>
 <div class="container body-content">
 @RenderBody()
 <hr />
 <footer>
 <p>&copy; @DateTime.Now.Year - Exceedone Co., Ltd.</p>
 </footer>
 </div>

@Scripts.Render("~/bundles/jquery")
 @Scripts.Render("~/bundles/bootstrap")
 @RenderSection("scripts", required: false)
</body>
</html>

bootstrap-fix-winphone8.js 

(function() {
 if ("-ms-user-select" in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) {
 var msViewportStyle = document.createElement("style");
 msViewportStyle.appendChild(
 document.createTextNode("@-ms-viewport{width:auto!important}")
 );
 document.getElementsByTagName("head")[0].appendChild(msViewportStyle);
 }
})();

SPA(Single Page Application) for knockout.js + TwitterBootstrap + Sammy.js + SQL Azure + Visual Studio2013

概要

Mobile(携帯)、PC/Tabletでも動作し、かつローカルアプリのように”さくさく”動かすためにには JavaScriptでSPAで、一生懸命かかないといけないです。

今回、簡単にSPA(Singe Page Application)ができるよに再利用可能な ベースクラスを作成してみました。

MITライセンスでご自由に改変して使ってください。

以下のサイトを事前にみておくと理解が早いです。

要件

  1. iPhone/Android/PC/Tabletで動作すること
    1. レシンポンシブルデザインにするためTwitterBootstrapを使用し各種端末に対応する。
    2. SPAの実現のために、JavaScriptを使用する
    3. Jquery + Knockout.js を使用して各種端末に対応する。
  2. 戻るボタン等のURL履歴に対応のこと
    1. Sammy.js により、URL履歴に対応する。
  3. デバックしやすくするためにVisual Studio 2013を使用する
  4. データベースはSQL Azureを使用する。
  5. WebサイトはWindows Azure 簡易Webサイトを使用する。

Demo Web Site(デモサイト)

http://vs-samples.azurewebsites.net/Home/SPA010

Program Source(プログラムソース)

サンプルコードは以下のGitHubに保存しました。

https://github.com/snoro/VS2013_MVC_Sample/tree/master/Backborn_Todos_WebAPI

イメージ

一覧画面

新規作成をクリックすると新規作成画面へ

明細をクリックすると更新画面へ

MVC5_VS2013_Knockout_SPA_0010

新規作成画面

MVC5_VS2013_Knockout_SPA_0020

更新画面

MVC5_VS2013_Knockout_SPA_0030

課題

  1. データの読み込み時、更新時にLoading画面を表示してボタンを押せないようにする。
  2. 一覧画面にページング処理をいれる。
  3. 明細データの取得や設定のコードを書かないでいいように改良する。
  4. 入力時に日付等にデートピッカーをつける

ソース抜粋

SPA010.cshtml.cshtml

@{
 ViewBag.Title = "Home Page";
}
<div id="disp" style="display:none">

<!--ko if: koSPAIsVisibleList -->
<div class="header">
 <h2>一覧</h2>
 <button type="button" class="btn btn-primary" data-bind="click: loadDetails">
 再読込
 </button>

<button type="button" class="btn btn-warning" data-bind="click: locationDetailCreate">
 新規作成
 </button>
 <br>
 <span class="label label-default">データ件数</span><span data-bind="text: koSPA_List().length"></span>
 <span class="label label-default">Message</span><span data-bind="text: koSPA_List_Message"></span>

</div>
<div class="results">
 <table class="table table-striped">
 <thead>
 <tr>
 <th>Id</th>
 <th>タイトル</th>
 <th>日付</th>
 <th></th>
 </tr>
 </thead>
 <tbody data-bind="foreach: koSPA_List">
 <tr data-bind="click: locationDetailUpdate">
 <td data-bind="text: Id"></td>
 <td data-bind="text: Title"></td>
 <td data-bind="text: Timestamp"></td>
 <td><span class="glyphicon glyphicon-chevron-right"></span></td>
 </tr>
 </tbody>
 </table>
</div>
<!--/ko-->

<!--ko if: koSPAIsVisibleDetail -->
<div class="header">
 <h2>詳細
 <!--ko if: koSPAIsVisibleDetail_Update -->
 (更新)
 <!--/ko-->
 <!--ko ifnot: koSPAIsVisibleDetail_Update -->
 (新規作成)
 <!--/ko-->
 </h2>

<button type="button" class="btn btn-primary" onClick="history.back(); return false;">
 <span class="glyphicon glyphicon-chevron-left"></span>戻る
 </button>
 <!--ko if: koSPAIsVisibleDetail_Update -->
 <button type="button" class="btn btn-warning" data-bind="click: updateDetailToDB">
 更新
 </button>
 <button type="button" class="btn btn-danger" data-bind="click: deleteDetailFromDB">
 削除
 </button>
 <!--/ko-->

<!--ko ifnot: koSPAIsVisibleDetail_Update -->
 <button type="button" class="btn btn-warning" data-bind="click: createDetailToDB">
 新規作成
 </button>
 <!--/ko-->

<br>
 <span class="label label-default">Message</span><span data-bind="text: koSPA_Detail_Message"></span>

</div>

<div class="results">

<table class="table">
 <thead>
 <tr>
 <th>項目名</th>
 <th>値</th>
 </tr>
 </thead>
 <tbody >
 <!--ko if: koSPAIsVisibleDetail_Update -->
 <tr>
 <td>Id</td>
 <td><span data-bind="text: koSPA_Detail.Id"></span></td>
 </tr>
 <!--/ko-->
 <tr>
 <td>Title</td>
 <td><input type="text" data-bind="value: koSPA_Detail.Title" /></td>
 </tr>
 <tr>
 <td>Detail</td>
 <td><input type="text" data-bind="value: koSPA_Detail.Content" /></td>
 </tr>
 <tr>
 <td>Timestamp</td>
 <td><input type="text" data-bind="value: koSPA_Detail.Timestamp" /></td>
 </tr>
 </tbody>
 </table>

</div>
<!--/ko-->

</div>

@section scripts{
 <script type="text/javascript" src="/Scripts/knockout-3.0.0.js"></script>
 <script type="text/javascript" src="/Scripts/sammy-0.7.4.js"></script>
 <script type="text/javascript" src="/Scripts/Users/knockout-SPA-0.1.1.js"></script>

<script type="text/javascript">
 $(document).ready(function () {
 //knockout SPAのパラメータを設定する
 //※このオブジェクトはNewする必要がないのでFunctionではない
 var koSPAParm = {
 url_List: "/api/Memo/",
 url_Detail_Create: "/api/Memo/",
 url_Detail_Update: "/api/Memo/",
 url_Detail_Delete: "/api/Memo/",

//Knockout Model
 koSPA_Model_Detail: function () {
 this.Id = ko.observable();
 this.Title = ko.observable();
 this.Content = ko.observable();
 this.Timestamp = ko.observable();
 },
 //Knockout View-Model
 koSPA_ViewModel : {
 koSPA_List: ko.observableArray(),
 koSPA_List_Message: ko.observable(""),
 koSPA_Detail: {
 Id: ko.observable(""),
 Title: ko.observable(""),
 Content: ko.observable(""),
 Timestamp: ko.observable(""),

 },
 koSPA_Detail_Message: ko.observable("")
 }

}
 //Using Post Detail to Database
 //when Detail-Create-Button is clicked
 //or Detail-Update-Button is clicked
 koSPAParm.koSPA_ViewModel.createDetailToObj = function () {
 var objDetail = {
 //ID不要
 Title: this.koSPA_Detail.Title(),
 Content: this.koSPA_Detail.Content(),
 Timestamp: this.koSPA_Detail.Timestamp()
 };

return objDetail
 }

//Using Initialize Detail
 //When List-Create-button is clicked
 koSPAParm.koSPA_ViewModel.initDetail = function () {
 this.koSPA_Detail.Id(-1);
 this.koSPA_Detail.Title("");
 this.koSPA_Detail.Content("");
 this.koSPA_Detail.Timestamp(new Date());
 }

//Using Set List to Detail
 //When List-Row is clicked
 koSPAParm.koSPA_ViewModel.setListToDetail = function (detailOfList) {
 this.koSPA_Detail.Id(detailOfList.Id());
 this.koSPA_Detail.Title(detailOfList.Title());
 this.koSPA_Detail.Content(detailOfList.Content());
 this.koSPA_Detail.Timestamp(detailOfList.Timestamp());

}
 //Using Set Detal to List
 //When Detail-Update-button is clicked
 koSPAParm.koSPA_ViewModel.setDetailToList = function (detailOfList) {
 detailOfList.Id(this.koSPA_Detail.Id());
 detailOfList.Title(this.koSPA_Detail.Title());
 detailOfList.Content(this.koSPA_Detail.Content());
 detailOfList.Timestamp(this.koSPA_Detail.Timestamp());
 }

//Using Add Detail to List
 //When List-Load-button is Clicked
 //or Detail-Create-button is clicked
 koSPAParm.koSPA_ViewModel.createDBDetailToObj = function (DBDetail) {
 var objDetail = new koSPAParm.koSPA_Model_Detail()
 .Id(DBDetail.Id)
 .Title(DBDetail.Title)
 .Content(DBDetail.Content)
 .Timestamp(DBDetail.Timestamp);
 return objDetail;

}
 //knockout SPAを実行する
 var koSPAApp = $.koSPA.CreateApp(koSPAParm);
 koSPAApp.run();
 document.getElementById("disp").style.display = "block";
 });
 </script>
}

 

knockout-SPA-0.1.1.js

/*
* Knockout SPA(Single Page Application)
* Created By Seiji Noro (https://github.com/snoro)
*
* Source:
* MIT License: http://www.opensource.org/licenses/MIT
*/
(function ($, window) {
 //knockout-3.0.0.jsでテスト済み
 if (typeof (ko) === undefined) { throw 'Knockout is required, please ensure it is loaded before loading this knockout-SPA'; }
 //sammy-0.7.4.jsでテスト済み
 if (typeof (Sammy) === undefined) { throw 'Sammy is required, please ensure it is loaded before loading this knockout-SPA'; }
 (function (factory) {
 //公開する関数をkoSPAだけにする

// Support module loading scenarios
 if (typeof define === 'function' && define.amd) {
 // AMD Anonymous Module
 define(['jquery'], factory);
 } else {
 // No module loader (plain <script> tag) - put directly in global namespace
 $.koSPA = window.koSPA = factory($);
 }
 })(function ($) {
 //koSPAオブジェクト
 var koSPA = {};
 koSPA.VERSION = '0.1.0';
 koSPA.location = window.location.toString();

//CreateApp
 koSPA.CreateApp = function () {
 var koSPAAppFunc = arguments[0];
 //koSPAApp オブジェクトをNewする。
 var koSPAApp = new koSPA.App(koSPAAppFunc);
 return koSPAApp;
 };

 //koSPAApp オブジェクト
 //初期値を設定するためにFunctionにしている。
 koSPA.App = function (koSPAParm)
 {
 //////////////////////////////////
 //knockout Model
 //////////////////////////////////
 //これはprototypeでないとメモリにコピーされるためよくない
 koSPAParm.koSPA_Model_Detail.prototype.locationDetailUpdate = function () {
 window.location.hash = "#/DetailUpdate/" + this.Id();
 };
 //////////////////////////////////
 //knockout View-Model
 //////////////////////////////////
 //thisに直接代入すると一覧取得のときにkoSPA_ViewModelを発見できない
 this.koSPA_ViewModel = koSPAParm.koSPA_ViewModel;

//knockout View-Model Property
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleList = ko.observable(true);
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleDetail = ko.observable(true);
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleDetail_Update = ko.observable(true);

&nbsp;

//knockout View-Model Method
 //(knockoutはViewModelのをprototypeで書くと、clickメソッド等でエラーにになる)
 koSPAParm.koSPA_ViewModel.loadDetails = function () {
 this.koSPA_List.removeAll(); // reset
 $.ajax({
 type: "GET",
 url: koSPAParm.url_List,
 dataType: "json"
 })
 .done(function (result) {
 $.each(result, function (i, p) {
 koSPAParm.koSPA_ViewModel.koSPA_List.push(
 koSPAParm.koSPA_ViewModel.createDBDetailToObj(p)
 );
 koSPAParm.koSPA_ViewModel.koSPA_List_Message("データを取得しました:" + new Date());
 });
 });
 };
 koSPAParm.koSPA_ViewModel.locationList = function () {
 window.location.hash = "#/List/";
 };
 koSPAParm.koSPA_ViewModel.locationDetailCreate = function () {
 window.location.hash = "#/DetailCreate";
 };
 koSPAParm.koSPA_ViewModel.createDetailToDB = function () {
 var dbDetailData = this.createDetailToObj();

$.ajax({
 type: "POST",
 url: koSPAParm.url_Detail_Create,
 contentType: "application/json;charset=UTF-8",
 dataType: "json",
 data: JSON.stringify(dbDetailData)
 })
 .done(function (result) {
 koSPAParm.koSPA_ViewModel.koSPA_List.push(
 koSPAParm.koSPA_ViewModel.createDBDetailToObj(result)
 );
 koSPAParm.koSPA_ViewModel.koSPA_Detail_Message("データを新規作成しました:" + new Date());
 })
 };
 koSPAParm.koSPA_ViewModel.updateDetailToDB = function () {
 var dbDetailData = this.createDetailToObj();

$.ajax({
 type: "POST",
 url: koSPAParm.url_Detail_Update + koSPAParm.koSPA_ViewModel.koSPA_Detail.Id(),
 contentType: "application/json;charset=UTF-8",
 dataType: "json",
 data: JSON.stringify(dbDetailData)
 })
 .done(function (result) {
 var detailOfList = koSPAParm.koSPA_ViewModel.findDetailFromList(koSPAParm.koSPA_ViewModel.koSPA_Detail.Id());
 koSPAParm.koSPA_ViewModel.setDetailToList(detailOfList)
 koSPAParm.koSPA_ViewModel.koSPA_Detail_Message("データを更新しました:" + new Date());
 })
 };

koSPAParm.koSPA_ViewModel.deleteDetailFromDB = function () {
 $.ajax({
 type: "DELETE",
 url: koSPAParm.url_Detail_Delete + koSPAParm.koSPA_ViewModel.koSPA_Detail.Id(),
 contentType: "application/json;charset=UTF-8",
 })
 .done(function (result) {
 koSPAParm.koSPA_ViewModel.removeDetailFromList(koSPAParm.koSPA_ViewModel.koSPA_Detail.Id());
 koSPAParm.koSPA_ViewModel.locationList();
 })
 };
 koSPAParm.koSPA_ViewModel.findDetailFromList = function (Id) {
 var detail = ko.utils.arrayFirst(this.koSPA_List(), function (t) {
 return Id === t.Id();
 }, this);
 return detail;
 };
 koSPAParm.koSPA_ViewModel.removeDetailFromList = function (Id) {
 this.koSPA_List.remove(function (item) {
 return item.Id() === Id;
 })
 };

//////////////////////////////////
 //Sammy
 //////////////////////////////////
 var SammyDipatcher = function (Method, Param1) {

switch (Method) {
 case "":
 case "List":
 koSPAParm.koSPA_ViewModel.koSPA_List_Message("");
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleList(true);
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleDetail(false);
 break;
 case "DetailUpdate":
 koSPAParm.koSPA_ViewModel.koSPA_Detail_Message("");
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleList(false);
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleDetail(true);
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleDetail_Update(true);

var detailFromList = koSPAParm.koSPA_ViewModel.findDetailFromList(Param1);
 koSPAParm.koSPA_ViewModel.setListToDetail(detailFromList);

break;
 case "DetailCreate":
 //koSPAParm.koSPA_ViewModel.koSPA_Detail_Message("");

koSPAParm.koSPA_ViewModel.koSPAIsVisibleList(false);
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleDetail(true);
 koSPAParm.koSPA_ViewModel.koSPAIsVisibleDetail_Update(false);

koSPAParm.koSPA_ViewModel.initDetail();

break;
 }
 }
 // Sammy を使ってブラウザの履歴に対応する(順番関係があるので、一番したにTOPを設置する。)
 this.SammyApp = $.sammy(function () {

//Method/Parm1
 this.get("#/:Method/:Param1", function () {
 var Method = this.params["Method"];
 var Param1 = parseInt(this.params["Param1"]);
 SammyDipatcher(Method, Param1);
 });

//Method
 this.get("#/:Method", function () {
 var Method = this.params["Method"];
 var Param1 = -1;
 SammyDipatcher(Method, Param1);
 });

//Topページ( location.hashが空だった時の処理)
 this.get("", function () {
//Ver 1.0.1 修正 同じサイト以外は移動する。
 var word = String(window.location);
 if (word.indexOf(koSPA.location) !== -1) {
 var Method = "";
 var Param1 = -1;
 SammyDipatcher(Method, Param1);
 }
 else {
 location.href = word;
 }

});
 });
 };
 //run
 koSPA.App.prototype = {
 run: function () {
 ko.applyBindings(this.koSPA_ViewModel);
 this.SammyApp.run();
 this.koSPA_ViewModel.loadDetails();

}
 };
 return koSPA;
 });
})(jQuery, window);

参考

// ]]>

Knockout.js でSQL Azureに一覧表示、データ更新のサンプル(VisualStudio2013 MVC5 with Twitterbootstrap)

前回

Knockout.js でSQL Azureに一覧表示、データ追加のサンプル(VisualStudio2013 MVC5 with Twitterbootstrap)

今回

Knockout.js でSQL Azureへ一覧表示とデータ更新するサンプルをVisual Studio2013で作りました。

以下はイメージです。

MVC5_VS2013_Knockout_WebAPI2_0010

Eidtボタンを押すと編集モードになります。

MVC5_VS2013_Knockout_WebAPI2_0020

Web Siteは以下です。

http://vs-samples.azurewebsites.net/Home/Knockout_WebAPI2

サンプルコードは以下のGitHubに保存しました。

https://github.com/snoro/VS2013_MVC_Sample/tree/master/Backborn_Todos_WebAPI

Knockout_WebAPI2.cshtml

@section scriptsHead {
<link rel="stylesheet" href="/Content/Users/todos.css" />
}

@{
 ViewBag.Title = "";
}

<br>
<br>
<br>
<br>
<br>
<h2>Knockout demo with asp.net web forms and asp.net webapi</h2>

<div id="dvemplist" data-bind="template: { name: templateDisplayed, foreach: Emps }"></div>
@section scripts{
 <script src="~/Scripts/knockout-3.0.0.js"></script>
 <!--Templates-->
 <!--View Template-->
 <script type="text/html" id="viewtemplate">
 <table class="table table-hover">
 <tr>
 <td>Id</td>
 <td>
 <span data-bind="text: $data.Id"></span>
 </td>
 </tr>
 <tr>
 <td>Name</td>
 <td>
 <span data-bind="text: $data.Name"></span>
 </td>
 </tr>
 <tr>
 <td>Salary</td>
 <td>
 <span data-bind="text: $data.Salary"></span>
 </td>
 </tr>
 <tr>
 <td>Depertment</td>
 <td>
 <span data-bind="text: $data.Depertment"></span>
 </td>
 </tr>
 <tr>
 <td>Memo</td>
 <td>
 <span data-bind="text: $data.Memo"></span>
 </td>
 </tr>
 <tr>
 <td>
 <button data-bind="click: function () { objEmpViewModel.editRecordtemplate($data); }">Edit</button>
 </td>
 </tr>
 </table>
 </script>
 <!--Ends Here-->

&nbsp;

<!--Edit Template-->
 <script id="edittemplate" type="text/html">
 <table class="table table-hover">
 <tr>
 <td>Id</td>
 <td>
 <input type="text" data-bind="value: $data.Id" disabled="disabled"/>
 </td>
 </tr>
 <tr>
 <td>Name</td>
 <td>
 <input type="text" data-bind="value: $data.Name"/>
 </td>
 </tr>
 <tr>
 <td>Salary</td>
 <td>
 <input type="text" data-bind="value: $data.Salary"/>
 </td>
 </tr>
 <tr>
 <td>Depertment</td>
 <td>
 <input type="text" data-bind="value: $data.Depertment"/>
 </td>
 </tr>
 <tr>
 <td>Memo</td>
 <td>
 <input type="text" data-bind="value: $data.Memo"/>
 </td>
 </tr>
 <tr>
 <td>
 <input type="button" id="btnsave" value="Save" data-bind="click: objEmpViewModel.Save" />
 </td>
 <td>
 <input type="button" id="btncancel" value="Cancel" data-bind="click: function () { objEmpViewModel.resettemplate(); }" />
 </td>
 </tr>
 </table>
 <!--Ends Here-->
 </script>
 <!--Ends Here-->

<script type="text/javascript">
 function EmpViewModel() {
 var self = this;

self.Emps = ko.observableArray([]);
 self.viewRecordtemplate = ko.observable("viewtemplate");
 self.editRecordtemplate = ko.observable();

self.templateDisplayed = function (t) {
 return t === self.editRecordtemplate() ? 'edittemplate' :
 self.viewRecordtemplate();
 };

self.Save = function (t) {
 var Employee = {};
 Employee.Id = t.Id;
 Employee.Name = t.Name;
 Employee.Depertment = t.Depertment;
 Employee.Salary = t.Salary;
 Employee.Memo = t.Memo;
 $.ajax({
 type: "PUT",
 url: "/api/Employee/" + Employee.Id,
 data: Employee,
 success: function (data) {
 alert("Record Updated Successfully ");
 self.resettemplate();
 },
 error: function (err) {
 alert("Error Occured, Please Reload the Page and Try Again " + err.status);
 self.resettemplate(t);
 }
 });
 };

self.resettemplate = function (t) {
 self.editRecordtemplate("viewtemplate");
 };
 }

var objEmpViewModel = new EmpViewModel();
 ko.applyBindings(objEmpViewModel);
 //初期データ読み込み
 $(document).ready(function () {
 $.ajax({
 type: "GET",
 url: "/api/Employee",
 //contentType: "application/json; charset=utf-8",
 dataType: "json",
 success: function (data) {
 //一度で全部設定する場合
 objEmpViewModel.Emps(data);
 },
 error: function (err) {
 alert("Error : " + err.status + " " + err.statusText);
 }
 });
 });
 </script>
}

参考

Simple data binding with Knockout, Web API and ASP.Net Web Forms

Building a Knockout.js based client for Web API services in an ASP.NET MVC app

Hello world, with Knockout JS and ASP.NET MVC 4!

Building Single Page Apps with ASP.NET MVC4 – Part 1 – Basic Desktop Application