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

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

前回

VisualStudio2013 MVC5 with Twitterbootstrapにおいて、Knockout.js でTodos のサンプルを組み込んでみた。

今回

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

以下はイメージです。

MVC5_VS2013_Knockout_WebAPI1_0010

Web Siteは以下です。

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

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

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

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

@{
 ViewBag.Title = "";
}

<br>
<br>
<br>
<br>
<br>
<h2>Knockout.js WebAPI(SQL Azure) with Twitterbootsrap</h2>

<input id="button_DataAdd" class="btn btn-primary" type="button" value="データ登録ボタン" />

&nbsp;

<table class="table table-hover">
<thead>
 <tr>
 <td>Id</td>
 <td>Name</td>
 <td>Depertment</td>
 <td>Salary</td>
 <td>Memo</td>
 </tr>
</thead>
<tbody data-bind="foreach: employees">
 <tr >
 <td data-bind="text: Id"></td>
 <td data-bind="text: Name"></td>
 <td data-bind="text: Depertment"></td>
 <td data-bind="text: Salary"></td>
 <td data-bind="text: Memo"></td>
 </tr>
</tbody>
</table>
@section scripts {
 <script src="~/Scripts/knockout-3.0.0.js"></script>

<script type="text/javascript">
 var objViewModel =
 {
 employees: ko.observableArray([])
 }

$(document).ready(function () {
 $.ajax({
 url: "/api/Employee",
 contentType: "text/json",
 type: "GET",
 success: function (data) {
 $.each(data, function (index) {
 //Pushを使う場合
 objViewModel.employees.push(ToKnockOutObservable(data[index]));
 });
 ko.applyBindings(objViewModel);
 },
 error: function (err) {
 alert("error occured Error No=" + err.status);
 }
 });
 function ToKnockOutObservable(employee) {
 return {
 Id: ko.observable(employee.Id),
 Name: ko.observable(employee.Name),
 Depertment: ko.observable(employee.Depertment),
 Salary: ko.observable(employee.Salary),
 Memo: ko.observable(employee.Memo)
 };
 }
 $("#button_DataAdd").click(function(){
 var Employee = {};
 Employee.Id = 0;
 Employee.Name = "test";
 Employee.Depertment = "testdepert";
 Employee.Salary = 100;
 Employee.Memo = "memo";

$.ajax({
 url: "/api/Employee",
 //contentType: "text/json",
 //contentType: 'application/json; charset=utf-8',
 type: 'POST',
 cache: false,
 data: Employee,
 //data: JSON.stringify(Employee),
 success: function (data) {
 alert("Record Add Successfully ");
 objViewModel.employees.push(ToKnockOutObservable(data));
 },
 error: function (err) {
 alert("Error Occured, Error No" + err.status);
 }

});
 });
 });
</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

MVC5 Single Page Application で作成したら Userの認証系をAPIで公開してくれる。

MVC5 Single Page Application で作成したら Userの認証系をAPIで公開してくれる。

これはなんだろうと思い、プロジェクトを作ってみると 認証系のAPIを全部Web API(Restful)で公開してくれている。

ログインから、パスワード変更やアカウント作成などをWebAPIから行うことが簡単にできる。

MVC5_SinglepageApplication_0010

 

APIの使い方もサンプルのアプリのメニューの[API]にあります。

MVC5_SinglepageApplication_0020

 

AccountController.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;
using WebApplication1.Models;
using WebApplication1.Providers;
using WebApplication1.Results;

namespace WebApplication1.Controllers
{
 [Authorize]
 [RoutePrefix("api/Account")]
 public class AccountController : ApiController
 {
 private const string LocalLoginProvider = "ローカル";

public AccountController()
 : this(Startup.UserManagerFactory(), Startup.OAuthOptions.AccessTokenFormat)
 {
 }

public AccountController(UserManager<IdentityUser> userManager,
 ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
 {
 UserManager = userManager;
 AccessTokenFormat = accessTokenFormat;
 }

public UserManager<IdentityUser> UserManager { get; private set; }
 public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }

// GET api/Account/UserInfo
 [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
 [Route("UserInfo")]
 public UserInfoViewModel GetUserInfo()
 {
 ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

return new UserInfoViewModel
 {
 UserName = User.Identity.GetUserName(),
 HasRegistered = externalLogin == null,
 LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
 };
 }

// POST api/Account/Logout
 [Route("Logout")]
 public IHttpActionResult Logout()
 {
 Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
 return Ok();
 }

// GET api/Account/ManageInfo?returnUrl=%2F&generateState=true
 [Route("ManageInfo")]
 public async Task<ManageInfoViewModel> GetManageInfo(string returnUrl, bool generateState = false)
 {
 IdentityUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());

if (user == null)
 {
 return null;
 }

List<UserLoginInfoViewModel> logins = new List<UserLoginInfoViewModel>();

foreach (IdentityUserLogin linkedAccount in user.Logins)
 {
 logins.Add(new UserLoginInfoViewModel
 {
 LoginProvider = linkedAccount.LoginProvider,
 ProviderKey = linkedAccount.ProviderKey
 });
 }

if (user.PasswordHash != null)
 {
 logins.Add(new UserLoginInfoViewModel
 {
 LoginProvider = LocalLoginProvider,
 ProviderKey = user.UserName,
 });
 }

return new ManageInfoViewModel
 {
 LocalLoginProvider = LocalLoginProvider,
 UserName = user.UserName,
 Logins = logins,
 ExternalLoginProviders = GetExternalLogins(returnUrl, generateState)
 };
 }

// POST api/Account/ChangePassword
 [Route("ChangePassword")]
 public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
 {
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }

IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword,
 model.NewPassword);
 IHttpActionResult errorResult = GetErrorResult(result);

if (errorResult != null)
 {
 return errorResult;
 }

return Ok();
 }

// POST api/Account/SetPassword
 [Route("SetPassword")]
 public async Task<IHttpActionResult> SetPassword(SetPasswordBindingModel model)
 {
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }

IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
 IHttpActionResult errorResult = GetErrorResult(result);

if (errorResult != null)
 {
 return errorResult;
 }

return Ok();
 }

// POST api/Account/AddExternalLogin
 [Route("AddExternalLogin")]
 public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
 {
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }

Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);

AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);

if (ticket == null || ticket.Identity == null || (ticket.Properties != null
 && ticket.Properties.ExpiresUtc.HasValue
 && ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
 {
 return BadRequest("外部ログインの失敗。");
 }

ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);

if (externalData == null)
 {
 return BadRequest("外部ログインは既にアカウントと関連付けられています。");
 }

IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
 new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));

IHttpActionResult errorResult = GetErrorResult(result);

if (errorResult != null)
 {
 return errorResult;
 }

return Ok();
 }

// POST api/Account/RemoveLogin
 [Route("RemoveLogin")]
 public async Task<IHttpActionResult> RemoveLogin(RemoveLoginBindingModel model)
 {
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }

IdentityResult result;

if (model.LoginProvider == LocalLoginProvider)
 {
 result = await UserManager.RemovePasswordAsync(User.Identity.GetUserId());
 }
 else
 {
 result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(),
 new UserLoginInfo(model.LoginProvider, model.ProviderKey));
 }

IHttpActionResult errorResult = GetErrorResult(result);

if (errorResult != null)
 {
 return errorResult;
 }

return Ok();
 }

// GET api/Account/ExternalLogin
 [OverrideAuthentication]
 [HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
 [AllowAnonymous]
 [Route("ExternalLogin", Name = "ExternalLogin")]
 public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
 {
 if (error != null)
 {
 return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
 }

if (!User.Identity.IsAuthenticated)
 {
 return new ChallengeResult(provider, this);
 }

ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

if (externalLogin == null)
 {
 return InternalServerError();
 }

if (externalLogin.LoginProvider != provider)
 {
 Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
 return new ChallengeResult(provider, this);
 }

IdentityUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
 externalLogin.ProviderKey));

bool hasRegistered = user != null;

if (hasRegistered)
 {
 Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
 ClaimsIdentity oAuthIdentity = await UserManager.CreateIdentityAsync(user,
 OAuthDefaults.AuthenticationType);
 ClaimsIdentity cookieIdentity = await UserManager.CreateIdentityAsync(user,
 CookieAuthenticationDefaults.AuthenticationType);
 AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
 Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
 }
 else
 {
 IEnumerable<Claim> claims = externalLogin.GetClaims();
 ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
 Authentication.SignIn(identity);
 }

return Ok();
 }

// GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
 [AllowAnonymous]
 [Route("ExternalLogins")]
 public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
 {
 IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
 List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();

string state;

if (generateState)
 {
 const int strengthInBits = 256;
 state = RandomOAuthStateGenerator.Generate(strengthInBits);
 }
 else
 {
 state = null;
 }

foreach (AuthenticationDescription description in descriptions)
 {
 ExternalLoginViewModel login = new ExternalLoginViewModel
 {
 Name = description.Caption,
 Url = Url.Route("ExternalLogin", new
 {
 provider = description.AuthenticationType,
 response_type = "token",
 client_id = Startup.PublicClientId,
 redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
 state = state
 }),
 State = state
 };
 logins.Add(login);
 }

return logins;
 }

// POST api/Account/Register
 [AllowAnonymous]
 [Route("Register")]
 public async Task<IHttpActionResult> Register(RegisterBindingModel model)
 {
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }

IdentityUser user = new IdentityUser
 {
 UserName = model.UserName
 };

IdentityResult result = await UserManager.CreateAsync(user, model.Password);
 IHttpActionResult errorResult = GetErrorResult(result);

if (errorResult != null)
 {
 return errorResult;
 }

return Ok();
 }

// POST api/Account/RegisterExternal
 [OverrideAuthentication]
 [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
 [Route("RegisterExternal")]
 public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
 {
 if (!ModelState.IsValid)
 {
 return BadRequest(ModelState);
 }

ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

if (externalLogin == null)
 {
 return InternalServerError();
 }

IdentityUser user = new IdentityUser
 {
 UserName = model.UserName
 };
 user.Logins.Add(new IdentityUserLogin
 {
 LoginProvider = externalLogin.LoginProvider,
 ProviderKey = externalLogin.ProviderKey
 });
 IdentityResult result = await UserManager.CreateAsync(user);
 IHttpActionResult errorResult = GetErrorResult(result);

if (errorResult != null)
 {
 return errorResult;
 }

return Ok();
 }

protected override void Dispose(bool disposing)
 {
 if (disposing)
 {
 UserManager.Dispose();
 }

base.Dispose(disposing);
 }

#region ヘルパー

private IAuthenticationManager Authentication
 {
 get { return Request.GetOwinContext().Authentication; }
 }

private IHttpActionResult GetErrorResult(IdentityResult result)
 {
 if (result == null)
 {
 return InternalServerError();
 }

if (!result.Succeeded)
 {
 if (result.Errors != null)
 {
 foreach (string error in result.Errors)
 {
 ModelState.AddModelError("", error);
 }
 }

if (ModelState.IsValid)
 {
 // 送信で使用できる ModelState エラーがないので、空の BadRequest を返します。
 return BadRequest();
 }

return BadRequest(ModelState);
 }

return null;
 }

private class ExternalLoginData
 {
 public string LoginProvider { get; set; }
 public string ProviderKey { get; set; }
 public string UserName { get; set; }

public IList<Claim> GetClaims()
 {
 IList<Claim> claims = new List<Claim>();
 claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));

if (UserName != null)
 {
 claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
 }

return claims;
 }

public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
 {
 if (identity == null)
 {
 return null;
 }

Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);

if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
 || String.IsNullOrEmpty(providerKeyClaim.Value))
 {
 return null;
 }

if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
 {
 return null;
 }

return new ExternalLoginData
 {
 LoginProvider = providerKeyClaim.Issuer,
 ProviderKey = providerKeyClaim.Value,
 UserName = identity.FindFirstValue(ClaimTypes.Name)
 };
 }
 }

private static class RandomOAuthStateGenerator
 {
 private static RandomNumberGenerator _random = new RNGCryptoServiceProvider();

public static string Generate(int strengthInBits)
 {
 const int bitsPerByte = 8;

if (strengthInBits % bitsPerByte != 0)
 {
 throw new ArgumentException("strengthInBits は 8 で割り切れる必要があります。", "strengthInBits");
 }

int strengthInBytes = strengthInBits / bitsPerByte;

byte[] data = new byte[strengthInBytes];
 _random.GetBytes(data);
 return HttpServerUtility.UrlTokenEncode(data);
 }
 }

#endregion
 }
}

VisualStudio2013 MVC5 with Twitterbootstrapにおいて、Knockout.js でTodos のサンプルを組み込んでみた。

前回

Backborn.jsのTodosアプリでAzure SQL(クラウド)+MVC5(VS2013)の組み込み。

今回

前回はBackborn.js(MVVMフレームワーク)+Todosアプリは、、今回はKnockout.jsにて作成してみます。

C#とかVBAとかに慣れている私にとっては、非常にとっつきやすく、とても簡単なのが印象的でした。

DOMの要素(button, input, だいたいなんでも) に data-bindすれば、 foreachや埋め込み変数など使えますし。

ko.observableをつかえば、データが変更したイベントを受け取りViewを更新することもできます。

以下はイメージです。

MVC5_VS2013_Knockout_Todos0010

Web Siteは以下です

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

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

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

/Views/Home/index.cshtml

@{
    ViewBag.Title = "Knockout_Todos";
}

<h2>Todo: Knockout.js with Twitterbootsrap</h2>
<div class="container">
    <input type="text" placeholder="Enter Todo" value="" data-bind="value: todoInputValue" />
    <button data-bind="click:addTodo">Add</button>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Todo</th>
                <th>Delete</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: todoList">

            <tr>
                <td data-bind="text: todoText"></td>
                <td><button class="btn btn-default" data-bind="click:$root.removeTodo">削除</button></td>
            </tr>
        </tbody>
    </table>
</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 TodoModel(value) {
        var self = this;
        self.todoText = ko.observable(value);
    }

    //ViewModelを定義
    function TestappViewModel() {
        var self = this;

        self.todoList = ko.observableArray(); //todoの配列
        self.todoInputValue = ko.observable(''); //todo 入力値

        //Todo追加
        self.addTodo = function (obj, e) {
            if (!self.todoInputValue()) return;

            //配列に追加する。
            self.todoList.unshift(new TodoModel(self.todoInputValue()));
            //入力値を消去する。
            self.todoInputValue('');
        };

        //To削除
        self.removeTodo = function (obj, e) {
            self.todoList.remove(obj);
        };
    }
    //binding (View - VIewMode)
    ko.applyBindings(new TestappViewModel());
});

参考

knockoutのホームページ

Knockout.js 日本語ドキュメント

Knockout-Bootstrap

Helping you select an MV* framework

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

clickサンプル

さくさくアプリ開発

Backborn.jsのTodosアプリでAzure SQL(クラウド)+MVC5(VS2013)の組み込み。

前回

VisualStudio2013 MVC5 with Twitterbootstrapにおいて、Backborn.js でTodos のサンプルを組み込んでみた。

今回

前回はBackborn.js(MVVMフレームワーク)+Todosアプリは、JavaScriptのローカル変数で動作するものでしたが、今回はデータをデータベース(SQL Server, Azure SQL)に保管してみます。

以下が完成したもの。Windows Azureでホスティング、 SQL Azureのデータベースを使用しています。

Web Siteは以下です。

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

 MVC5_VS2013_Backborn_SQLAzure0010

前回作成した、Todo に以下の処理を行います。

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

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

MVC5のRESTfulの、サービスを実装します。このサーバ再度のWeb APIでクライアントサイドのBackborn.js と通信します。

  1. データの型を定義します。 Models/Todo.cs
    ※重要1)ビルドしておきます。 ビルドしていないと次の新規作成のときにClassを参照できない
    ※重要2)Entity Frameworkのバージョンの挙動の違い
    title, order, done はBackborn.js のTodoモデルでは頭が小文字なので、
    C#側のモデルも頭を小文字にしておかないといけない。
    Entity framework5(VS2012)は頭が大文字でも小文字に自動的にしているようですが、
    Entity Framework6(VS2013)から頭がそのまま、大文字でくるのでBackborn.js側で違う項目と判断して、データ表示がうまくできないです。
    MVC5_VS2013_Backborn_SQLAzure0020
  2. 新しくスキャルフォールディングされたアイテムを選択します。
    MVC5_VS2013_Backborn_SQLAzure0030
  3. 「Entity Framework を使用したアクションがあるWeb API2 コントローラー」を選択します。
    MVC5_VS2013_Backborn_SQLAzure0040
  4. 完了すると「ApiController(TodosController)」が作成されます。
    MVC5_VS2013_Backborn_SQLAzure0050
  5. RESTfulなApiControllerがきのうするか実行してみます。
    テストURL
    http://localhost:50262/api/todos
    以下のようにJSONがダウンロードされてきます。
    MVC5_VS2013_Backborn_SQLAzure0060
    ※ちなみに、Entity Frameworkのコードファーストなので、自動的にテーブルも作成されます。
    MVC5_VS2013_Backborn_SQLAzure0070
  6. Backborn.js のTodoのモデルをローカルストレージからRestfulなAPIに変更します。

    //localStorage: new Backbone.LocalStorage(“todos”),
    url: function () { return ‘/api/todos’; },

    MVC5_VS2013_Backborn_SQLAzure0080

  7. これで実行すると以下のようにローカルのSQLサーバと接続されます。
    MVC5_VS2013_Backborn_SQLAzure0010
  8. SQL Azureに接続を変更します。
    Windows Azure管理ポータルでデータベースを作成します。
    まずはローカルでテストしたいので、「この IP アドレス用に Windows Azure ファイアウォール ルールを設定する」を設定しておきます。
    その後、「ADO.Net、ODBC、PHP、および JDBC の SQL データベース接続文字列を表示する」で接続文字列を表示します。
    MVC5_VS2013_Backborn_SQLAzure0090
  9. ADO.NET を接続文字列をコピーします。
    MVC5_VS2013_Backborn_SQLAzure0110
  10. Web.config の接続文字列を以下のようにAzureように書き換えます。
    MVC5_VS2013_Backborn_SQLAzure0120
    <connectionStrings>

    <!–Local DB–>
    <!–<add name=”Backborn_Todos_WebAPIContext” connectionString=”Data Source=(localdb)\v11.0; Initial Catalog=Backborn_Todos_WebAPIContext-20131031155459; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|Backborn_Todos_WebAPIContext-20131031155459.mdf” providerName=”System.Data.SqlClient” />–>

    <!–SQL Azure–>
    <add name=”Backborn_Todos_WebAPIContext” connectionString=”Server=tcp:XXXXXXXXXX.database.windows.net,1433;Database=ZZZZZZZZZZ;User ID=EAPDB_User@XXXXXXXXXX;Password=YYYYYYYYYY;Trusted_Connection=False;Encrypt=True;Connection Timeout=30″ providerName=”System.Data.SqlClient” />

    </connectionStrings>

  11. 成功すると以下のようにWindows Azure Webサイト+SQL AzureでBackborn.js Todosアプリが動作します。
    http://vs-samples.azurewebsites.net/Home/Todos_WebAPI
    MVC5_VS2013_Backborn_SQLAzure0130

参考

ASP.NET Backbone Template 

Integrating Backbone.js with ASP.NET Web API

Single Page Application with Backbone.js and ASP.NET Web API

VisualStudio2013 MVC5 with Twitterbootstrapにおいて、Backborn.js でTodos のサンプルを組み込んでみた。

Visual Studio 2013 から MVC5でプロジェクトを作成するとデフォルトでTwitterBootstrap(CSS)のきれいなデザインが組み込まれます。

今回は、Visual Studio でBackborn.jsでサンプルが少ないことから(Knockout.jsは多いと思います)作成してみました。

※Backborn.js は有名なMVVMのフレームワークですので、省略します。

1. まずは、Backborn.jsのサンプルでおなじみのTodosを作ってみることにします。

http://localtodos.com/

Backborn_todos_0010

2.日本語の解析済みのサンプルソースコードはGitHubにありますので、これを参考にしながら、Visual Studio2013で作成します。

https://github.com/yuku-t/backbone-sample

3. Visual Studio 2013 でサンプルを作成してみました。

※私のサンプルは以下のGitHubにあげました!

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

Backborn_todos_0020

4.Windows Azureの 実行結果は以下のようになります。

実際のWeb Site (Windows Azure)は以下です。

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

もとのデザインが、TwitterBootstarpでないので、デザインが壊れましたので?

いや、たぶん、HTML TemplateがMVC5にコピペしただけではだめそう?

後でTwitterBootstrapのデザインをきれいに修正しますが、こんな感じで動きます。

Backborn_todos_0030

5. 文字化けしていた原因がわかりました。

underscore.js のテンプレートを使用しているのですが。

Visual Studio にサンプルを張り付けると、自動でHTMLのソースを整形機能がきいていて、下記の赤い部分が自動で補完されていて表示が、化けていました。

※HTML貼り付け時、checkboxの後ろに自動的に [>]が補完される。

<input type=”checkbox”> <%= done ? ‘checked=”checked”‘ : ” %> />

後で、HTMLを手修正するか、Visual Studioの ツール/オプションの

テキストエディター/HTML/詳細設定 の貼り付けをFalseにすると自動成型しないです。

Backborn_todos_0040

モダンWebサイト(One ASP.NET)の新しい認証システム(クレームベース認証対応)

標準でクレームベース認証のサポート、まってました!! 

概要

Visual Studio 2013 から 新しい ASP.NET Identity が使えるようになった。

この認証システム基盤はマイクロソフトの戦略、One ASP.NET(マルチデバイス対応やモダンWebを実現するシステム)に基づいているようです。

Microsoft Blog (原文)

Introducing ASP.NET Identity – A membership system for ASP.NET applications

特徴としては、

  1. クレームベース認証(Claims Based)のサポート
    古いけどこの辺がわかりやすいかも。
    マイクロソフトのアイデンティティに関するビジョン
  2. ログイン連携
    Microsoft Account, Facebook, Twitter and Google 、独自認証に対応
  3. 権限管理
    認証済みかどうかだけではなく、ユーザの権限によってアクセス制限できる。

試しに Visual Studio 2013 RC でMVC5 プロジェクトを作成すると、認証系がそれで実装されます。

イメージは以下のような感じ

MVC5_Autholization

ただ。 現在 Viausl Studio 2013 がまだRCである。

ということで、検索してもほとんど情報がないので、以下のような機能がアプリでは必要になるが自力で実装しないといけない。

機能例:ユーザがパスワード忘れたときに、リセットするサンプルがないので自作しないといけない。

以下の関数が使えそうだが、マニュアルすらまだないので手探りで作らないといけない。

Microsoft.AspNet.Identity

PasswordManagerExtensions

ResetPassword(this IPasswordManager manager, string tokenId, string newPassword);

以下が認証系で使用しているライブラリ

using System.Security.Claims;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;

Office用アプリ(Apps for Office)にクールなデザイン(Twitterbootstrap)を適用し, Facebook, Google等とログイン連携してみた。

概要

実際の製品としてアプリを作るには、いろんな細かいところを作らなければならないので、アプリを作成し検証してみました。

今回作成したサンプルは、後でGithub/CodeprexにUPする予定です。

詳細

ちなみに、今回はVisual Studio 2013 RC1 を使い開発しました。

なぜならば! 開発(プロジェクトテンプレート・デバック等)がかなり簡単だからだ!!

実際、わたしはこのコードを今日1日で書き上げることができた!!!

だから、頑張れば誰でも出来るんです!!!(言い過ぎね!)

わからなければ、わたしが、勉強会でやりますので来てくださいね、たぶん。

環境

  1. Microsoft Office 2013
  2. twitterBootstrap
  3. Visual Studio2013 RC1 with MVC5
  4. Windows Azure, SQL Azure
  5. Login連携(Twitter, Facebook, Microsoft ID, Google)

イメージ

Office用アプリ(Apps for Office)の個別機能画面(見積、不動産管理、等の個別の機能)

MVC5_OFFICEAPPS_0010

WebSiteの共通画面(認証、権限、顧客、等のマスター)

※Office用アプリと同じWebサーバだかASP.NET MVC5の機能により、Layout テンプレートを変更しているで、メニュー、レイアウトとか簡単に入れ替え可能

MVC5_OFFICEAPPS_0020

Visual Studio 2013 RC1 のプロジェクト及び、データベースの構成

※Officeアプリ用のテンプレートを ASP.NET MVC5で作ります。

MVC5_OFFICEAPPS_0030

まずは機能要件を整理してみよう。

  1. Tablet 等のモバイルにも対応するため、クッキーレス(cookieless)であること
    iPhone/iPad/Androidでも問題なく動作
  2. 様々なアカウントと連携したログイン機能を有すること(独自認証、Facebook, Google, Twitter, Microsoft ID)
  3. 整理されたメニューが必要
  4. Office用アプリの個別機能画面(見積、不動産管理、等の個別の機能)
  5. WebSiteの共通画面(認証、権限、顧客、等のマスター)

そして非機能要件も整理してみよう。

  1. デザインがかっこいい!(twitterbootstrap)
  2. クラウドで動く(Windows Azure, Azure Web Site)
    とりあえず、簡単にWebサーバをスケールアウト(増設)できるように、にセッションなしで開発しておこう!!

細かいのは順次ブログにかきますが。ログインのイメージを掲載しておきます。

ログイン-Office用アプリの個別機能画面(見積、不動産管理、等の個別の機能)

MVC5_OFFICEAPPS_0040

ログイン-WebSiteの共通画面(認証、権限、顧客、等のマスター)
※Office用アプリと同じWebサーバだかASP.NET MVC5の機能により、Layout テンプレートを変更しているで、メニュー、レイアウトとか簡単に入れ替え可能

MVC5_OFFICEAPPS_0050

次回は細かいプログラム解説やるつもりです!!