Windows Azure Webサイト すごい

Widnows Azure Webサイトなにがすごいって、

自分でつかっていて、状況によってサーバの性能を動的にかえられるのはいいですよ!!

1.価格は自分でサーバつくるよりはるかにやすい。

  • 専用サーバでもSサイズ(cpu 1/メモリ1.75G)で 8.31円/1時間 + 転送量(9円1G)
  • 詳細は以下

2. ものの1分でWebサイトが立ち上がる。

3. Webサイトのサービス提供したまま、動的に、サーバの性能(サイズ、数)を動的に増減できる。

  • 無料 <–> 有料に動的に変更できる。
  • 共有、標準については、サーバ数6個まで動的に増減できる。(ロードバランサー価格込み!!!)
  • WidnowsAzureWebSite_0010

 

4. サーバの負荷等もグラフで表示

  • WidnowsAzureWebSite_0020

5. 2014年、東京と大阪にデータセンターがやってくる。

SQL Azure でデータベースコピー

概要

SQL Azure でデータコピーをしてみました。

ポイントは master データベースを選択して実行することです。

今回は

手順

「SQL データベース」 を選択後、「サーバ」を選択する。

sqlazureDBCopy_0010

「サーバの管理」を選択します。

sqlazureDBCopy_0020

「データベース」を入力しないで「ユーザー名」「パスワード」を入力します。

sqlazureDBCopy_0030

「管理」を選択します。

sqlazureDBCopy_0040

データベースをコピーする場合は、必ず「master」を選択します。

sqlazureDBCopy_0050

「test 」というデータベースを「applia」 にコピーします。

CREATE DATABASE applia AS COPY OF test

※既に、存在するとエラーになります。

sqlazureDBCopy_0060

「データベース」一覧をみるとコピーが開始されています。

sqlazureDBCopy_0070

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

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
 }
}