静的コンテンツへの要求を動的に処理する

前回の静的コンテンツにも SSL を強制するという投稿に関連して、Web アプリケーションにおいて
.txt などの静的ファイルの拡張子を持った HTTP 要求を動的に処理する方法について書きます。

これを実現するには、特定の拡張子に対する HTTP ハンドラーを新たに実装してもよいのですが、
ASP.NET MVC のルーティングを使って、特定の拡張子に対するアクションを手軽に実装できます。

拡張子 .txt の付いた HTTP 要求に対するアクション メソッドを、次のように属性ルーティングを使って実装します。


using System;
using System.Web;
using System.Web.Mvc;

namespace HandlersMvc.Controllers
{
    [RoutePrefix("Text")]
    [Route("{action=Index}")]
    public class TextController : Controller
    {
        public ActionResult Index()
        {
            return View();
        } 

        [Route("{name}.txt")]
        public ActionResult GetFile(string name)
        {
            var text = string.Format("The file name is {0}.", name);
            return Content(text, "text/plain");
        }
    }
}


 

属性ルーティングを有効にするために、RouteConfig.cs で MapMvcAttributeRoutes メソッドを呼び出します。


using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace HandlersMvc
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapMvcAttributeRoutes();

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}


 

しかし、これだけでは動作しません。

静的コンテンツの拡張子

 

これは前回の静的コンテンツにも SSL を強制するの投稿で説明した通り、
静的コンテンツの拡張子に対する HTTP 要求は IIS 側で処理されるためです。
そこで、Web.config で特定の拡張子に対する HTTP ハンドラーの指定を追加します。


<configuration>
  <system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="Custom-TextHandler" path="*.txt" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
</configuration>


 

これで、静的コンテンツの拡張子に対しても動的に処理できるようになりました。

静的コンテンツの拡張子

 

なお、前回のような SSL の強制と今回のような ASP.NET MVC での動的処理を組み合わせたい場合、
Web.config では前回と同様の記述で、次の 2 つを指定すれば動作するようです。

path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler"
path="*" verb="*" type="System.Web.StaticFileHandler"

 

バージョン情報
.NET Framework 4.5
ASP.NET MVC 5

参照
ハンドラー <handlers> (TechNet)
静的コンテンツにも SSL を強制する

カテゴリー: .NET Framework. タグ: . 1 Comment »

静的コンテンツにも SSL を強制する

以前に Windows Azure Web サイトで SSL を強制するというエントリを書きましたが、
この方法は ASP.NET MVC でハンドルされる HTTP 要求に対してのみ有効でした。
今回は、HTML/CSS/JS ファイルあるいはテキスト ファイルといった静的コンテンツに対しても、
SSL での通信を強制する方法について考えてみます。

 

まず、既定の状態では、
静的コンテンツに対する HTTP 要求は IIS 側での処理で完結してしまい、ASP.NET 側には到達しません。
ASP.NET MVC 5 の Web.config の HTTP ハンドラーの設定では、
拡張子のない HTTP 要求は ASP.NET で処理されるように構成されていますが、
拡張子の付いた HTTP 要求についてはとくに指定されていません。

そこで次のように、すべての HTTP 要求を ASP.NET で処理するように構成します。
すべてのパス (path="*") に対する HTTP ハンドラーとして System.Web.StaticFileHandler を指定します。


<configuration>
  <system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="Custom-StaticFileHandler" path="*" verb="GET" type="System.Web.StaticFileHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
</configuration>


 

次に、HTTP でアクセスされた場合のフィルタリングについてです。
ASP.NET MVC で処理される場合はカスタム フィルター属性を利用できますが、
StaticFileHandler では当然それを利用できません。

そこで、HTTP 要求の最初に発生するイベントである HttpApplication.BeginRequest イベントを利用します。
Global.asax.cs で、次のようにイベント ハンドラーを追加します。


using System;
using System.Configuration;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace HandlersMvc
{
    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }

        void Application_BeginRequest(object sender, EventArgs e)
        {
            if (!Request.IsSecureConnection)
            {
                var url = Request.Url.AbsoluteUri;
                var secureUrl = Regex.Replace(url, @"^\w+(?=://)", Uri.UriSchemeHttps);

                if (PermanentHttps)
                {
                    Response.RedirectPermanent(secureUrl, true);
                }
                else
                {
                    Response.Redirect(secureUrl, true);
                }
            }
        }

        static bool PermanentHttps
        {
            get { return Convert.ToBoolean(ConfigurationManager.AppSettings["app:PermanentHttps"]); }
        }
    }
}


プロトコルが HTTPS でない場合は、HTTPS の URL にリダイレクトさせます。
これで、静的コンテンツも含め、すべての HTTP 要求に対して SSL を強制できるようになりました。

HTML/CSS/JS ファイル

テキスト ファイル

Web.config などのファイルにはアクセスできません。

構成ファイル

 

なお、今回の例では、
永続的なリダイレクト (301) か一時的なリダイレクト (302) かを Web.config で指定できるようにしています。


<configuration>
  <appSettings>
    <add key="app:PermanentHttps" value="false" />
  </appSettings>
</configuration>


Azure Web サイトでは、管理ポータルの [構成] で設定値をオーバーライドすることもできます。

Azure Web サイトの構成

永続的なリダイレクト (301)

 

バージョン情報
.NET Framework 4.5
ASP.NET MVC 5

参照
HTTPS および SSL による Windows Azure Web サイト (WAWS) のセキュリティ保護
ハンドラー <handlers> (TechNet)
Windows Azure Web サイトで SSL を強制する
静的コンテンツへの要求を動的に処理する

Azure Web サイトに Dropbox からデプロイする

前回は Azure と GitHub で継続的インテグレーションについて書きましたが、
今回はソースコードを格納するストレージとして Dropbox を利用します。

 

■ Azure Web サイトを作成する

例によって、Microsoft Azure の管理ポータルで、新規の Web サイトを [カスタム作成] を選択して作成します。
[ソース コードの位置] で [Dropbox] を選択します。

image

image

Dropbox の承認が完了したら、[フォルダー名] を選択します。
新しいフォルダーを作成することもできます。

image

Dropbox の「アプリ\Azure」フォルダーの下に、指定したフォルダーが作成されます。

image

 

■ Web アプリケーションをコピーする

このフォルダーに、 ASP.NET MVC Web アプリケーションのソースファイルをコピーします。
bin フォルダーを含める必要はありません。

image

Dropbox と連携している場合、Azure へのデプロイは自動では開始しません。
Web サイトの [デプロイ] タブで、[同期] をクリックします。

image

デプロイが開始されます。

image

デプロイが完了しました。

image

次に、ソースコードの「Version 1」の部分を「Version 2」に変更して、再び [同期] をクリックします。
すると、差分だけが同期され、デプロイが実行されます。

image

image

image

 

デプロイの仕組みは GitHub のときと同様で、
Kudu によってソースコードを取得し、ビルドを実行して、Web サイトにコピーする、という一連のタスクが実施されます。

カテゴリー: ALM, クラウド. タグ: , . 1 Comment »

Azure と GitHub で継続的インテグレーション

以前に Azure と Visual Studio Online で継続的インテグレーションというエントリを書きましたが、
今回はバージョン管理システムとして GitHub を利用した場合の
Azure での継続的インテグレーション (Continuous Integration, CI) について記述します。

 

■ リポジトリを作成する

まず、GitHub でリポジトリを作成します。
下の図ではプライベート リポジトリを作成していますが、
パブリック リポジトリでもプライベート リポジトリでも以降の手順は同じです。

image

 

■ Azure Web サイトを作成する

Microsoft Azure の管理ポータルで、新規の Web サイトを、[カスタム作成] を選択して作成します。
[ソース管理から発行] をオンにします。

image

[GitHub] を選択します。

image

GitHub の承認が完了したら、[リポジトリ名] を選択します。
[リポジトリ名] の選択肢には、プライベート リポジトリも同じように含まれます。

image

 

Web サイトの作成が完了すると、現在のリポジトリの内容がデプロイされます。

image

まだ Web アプリケーションのソースコードが含まれていないため、Web サイトはこのような表示になります。

image

GitHub のリポジトリの [Settings] で、Azure と連携していることが確認できます。

image

 

■ Web アプリケーションをコミットする

ASP.NET MVC で Web アプリケーションを作成して、GitHub に commit/push します。
bin フォルダーを含める必要はありません。
commit/push が完了すると、自動で Web アプリケーションのデプロイが開始します。

image

デプロイが完了しました。

image

以降も、ソースコードを変更して GitHub に commit/push するだけで、自動的にデプロイされます。

image

 

■ デプロイの仕組み

今回の手順では、Visual Studio Online の場合とは異なり、ビルドのタスクを設定していません。

Azure Web サイトの内部では Kudu というツールが動作しており、
GitHub や Dropbox と連携している場合にデプロイが開始されると、
ソースコードを取得し、ビルドを実行して、Web サイトにコピーする、という一連のタスクを実施します。

Kudu には管理サイトがあります。
Kudu の管理サイトにアクセスするには、まず Web サイトのダッシュボードで [デプロイ資格情報] を設定します。

image

Kudu の管理サイトは、

https://site-name.scm.azurewebsites.net/

のように、サイト名のあとに「scm」を追加すればアクセスできます。表示されない場合は、

https://site-name.scm.azurewebsites.net/deploy

も試してみてください。

Kudu の管理サイトでは、エクスプローラーのようにファイルにアクセスできます。

image

また、Web サイトの [デプロイ] ページでも、デプロイのログを見ることができます。

image

これらから判断すると、デプロイ時には差分のファイルのみを取得してビルドし、
ビルド結果の差分のファイルのみを Web サイトにコピーしているように見えます。
したがって Immutable Infrastructure ではなく、変更点が少なければデプロイが速く完了します。

前回: Azure と Visual Studio Online で継続的インテグレーション
次回: Azure Web サイトに Dropbox からデプロイ

参照
Kudu (GitHub)

カテゴリー: ALM, クラウド. タグ: , . 3 Comments »

ASP.NET MVC で JSONP

前回の ASP.NET MVC で CORS では、
JSON Web サービスへのクロスドメインでのアクセスを CORS で実現するための方法を紹介しました。
今回はもう 1 つの方法である JSONP について説明します。

JSONP は、データを JSON ではなく、JavaScript として受け取って実行するという、
トリック的な方法でクロスドメインでのアクセスを実現する仕組みです。

具体的には、Ajax 通信完了後の処理を次のような関数として定義しておき、

function callbackFunc(data) {
    // 通信後の処理。
    // data には、Web サービスの戻り値であるオブジェクトが渡されます。
}

このコールバック関数の名前を URL のクエリ文字列で渡します。

/json/api123?callback=callbackFunc

この HTTP 要求に対し、サーバー側からの HTTP 応答では次のように (JSON ではなく) JavaScript を返します。

callbackFunc({ prop1: 123, prop2: "value123" });

このスクリプトをブラウザー上で実行させることで、コールバック関数を呼び出すという仕組みです。

 

この仕組みを ASP.NET MVC で実現するには、
前回の ASP.NET MVC で CORS で作成したコントローラーを次のように変更します。
クエリ文字列の中に callback が含まれている場合は JavaScript を作成して返します。


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

namespace CorsMvc.Controllers
{
    public class JsonController : Controller
    {
        // GET /json/uuid
        public ActionResult Uuid()
        {
            var data = Guid.NewGuid();
            return Json(data);
        }

        new ActionResult Json(object data)
        {
            Response.Headers["Expires"] = "-1";
            Response.Headers["Access-Control-Allow-Origin"] = "*";

            var callback = Request.QueryString["callback"];

            if (string.IsNullOrWhiteSpace(callback))
            {
                return Json(data, JsonRequestBehavior.AllowGet);
            }
            else
            {
                var serializer = new JavaScriptSerializer();
                var json = serializer.Serialize(data);
                return JavaScript(string.Format("{0}({1});", callback, json));
            }
        }
    }
}


 

次に、クライアントとなる HTML を記述します。
jQuery を利用する場合は、jQuery.getJSON メソッドの引数の URL を、

/json/uuid?callback=?

のように、コールバック関数の名前が入る部分を ? にしておきます。
すると、jQuery の内部でコールバック関数の定義が作成され、? の部分はその名前で置き換えられます。


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>JSONP Test</title>
    <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js" type="text/javascript"></script>
</head>
<body>
    <div>
        <h1>JSONP Test</h1>
        <h3>GET /json/uuid</h3>
        <button id="GetUuidButton">Generate</button>
        <pre id="GetUuidResult"> </pre>
    </div>
    <script type="text/javascript">
        $(function () { 
            $("#GetUuidButton").click(function () {
                $.getJSON("http://localhost:58201/json/uuid?callback=?", function (data) {
                    $("#GetUuidResult").text(data);
                });
            });
        });
    </script>
</body>
</html>


 

これで、JSONP によるクロスドメイン通信ができるようになりました。

クロスドメインでのアクセス

なお jQuery では、GET でアクセスする場合のみこの方法が有効になるようです。

 

バージョン情報
ASP.NET MVC 5.1.2
jQuery 2.1.0

参照
jQuery.getJSON() – JSONP
jQuery $.getJSONによるJSON・JSONP読み込み
ASP.NET MVC で CORS

カテゴリー: .NET Framework. タグ: . 1 Comment »

ASP.NET MVC で CORS

現在の ASP.NET テクノロジで JSON Web サービスを構築する場合、
RESTful な API にするには ASP.NET Web API を、
RPC スタイルの API にするには ASP.NET MVC を利用することと思います。

また、JSON Web サービスへのクロスドメインでのアクセスを許可するには、
Cross-Origin Resource Sharing (CORS) または JSONP をサポートする必要があります。

CORS をサポートするためのライブラリは、

として提供されています。
しかし、ASP.NET Web API ではこれを利用できるのですが、ASP.NET MVC では利用する方法がないようです。

そこで以下では、ASP.NET MVC で CORS を自前でサポートする方法について考えます。

 

まず、ASP.NET MVC で JSON Web サービスを実装するには、次の例のようにコントローラーを作成します。
GET と POST でサービスを実装します。


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

namespace CorsMvc.Controllers
{
    public class JsonController : Controller
    {
        // GET /json/uuid
        public ActionResult Uuid()
        {
            var data = Guid.NewGuid();
            return Json(data);
        }

        // POST /json/uuid
        [HttpPost]
        public ActionResult Uuid(int count)
        {
            var data = Enumerable.Repeat(false, count).Select(_ => Guid.NewGuid()).ToArray();
            return Json(data);
        }

        new ActionResult Json(object data)
        {
            Response.Headers["Expires"] = "-1";

            return Json(data, JsonRequestBehavior.AllowGet);
        }
    }
}


 

この Web サービスをブラウザーで確認するには、次のような HTML を作成します。
まずは同一ドメインの場合です。


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>JSON Test</title>
    <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js" type="text/javascript"></script>
</head>
<body>
    <div>
        <h1>JSON Test</h1>
        <h3>GET /json/uuid</h3>
        <button id="GetUuidButton">Generate</button>
        <pre id="GetUuidResult"> </pre>
        <h3>POST /json/uuid</h3>
        <button id="PostUuidButton">Generate</button>
        <pre id="PostUuidResult"> </pre>
    </div>
    <script type="text/javascript">
        $(function () {
            $("#GetUuidButton").click(function () {
                $.getJSON("/json/uuid").done(function (data) {
                    $("#GetUuidResult").text(data);
                });
            });
            $("#PostUuidButton").click(function () {
                $.post("/json/uuid", { count: 3 }).done(function (data) {
                    $("#PostUuidResult").text(data);
                });
            });
        });
    </script>
</body>
</html>


 

これで、Web サービスの動作を確認できます。

同一ドメインでのアクセス

 

次に、Web サービスの呼び出し先の URL を次のように変更して、異なるドメインに HTML を配置して実行します。


    <script type="text/javascript">
        $(function () {
            $("#GetUuidButton").click(function () {
                $.getJSON("http://localhost:58201/json/uuid").done(function (data) {
                    $("#GetUuidResult").text(data);
                });
            });
            $("#PostUuidButton").click(function () {
                $.post("http://localhost:58201/json/uuid", { count: 3 }).done(function (data) {
                    $("#PostUuidResult").text(data);
                });
            });
        });
    </script>


 

すると、Web サービスの呼び出しに失敗します。
実際には通信は発生しているのですが、ブラウザーにより不可と判断されます。

CORS でクロスドメインでのアクセスを許可するには、
HTTP 応答の Access-Control-Allow-Origin ヘッダーにアクセスを許可するドメインを指定する必要があり、
ASP.NET MVC の場合は次のように自前でこの処理を実装します。
最初にコントローラーの中で作成した Json メソッドに 1 行追加するだけです。


new ActionResult Json(object data)
{
    Response.Headers["Expires"] = "-1";
    Response.Headers["Access-Control-Allow-Origin"] = "*";

    return Json(data, JsonRequestBehavior.AllowGet);
}


 

これで、クロスドメインで Web サービスにアクセスできるようになりました。

クロスドメインでのアクセス

 

GET の場合も POST の場合も、プレフライト要求が発生する条件に当てはまらないようなシンプルな要求の場合、
Access-Control-Allow-Origin ヘッダーのみで対応ができます。
また、プレフライト要求が発生する場合も、
今回のように必要なヘッダーを HTTP 応答に追加することで実現できると思います。

 

また、次のように Web.config でカスタム ヘッダーを設定する方法もあります。
この場合、すべての HTTP 応答に対してカスタム ヘッダーが追加されることになります。


<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>


 

次回に続きます。
ASP.NET MVC で JSONP

バージョン情報
ASP.NET MVC 5.1.2

参照
HTTP access control (CORS)
CORS(Cross-Origin Resource Sharing)について整理してみた
ASP.NET Web API 2 における CORS サポート
Windows Azure ストレージ: CORS の導入
ASP.NET Web API 2 CORS サポートについて
カスタム ヘッダー <customHeaders> (TechNet)
HTTP Protocol Settings <httpProtocol>

カテゴリー: .NET Framework. タグ: . 1 Comment »

ASP.NET MVC 5 へのアップグレード

Visual Studio 2012 で、
ASP.NET MVC 4 の Web アプリケーションを ASP.NET MVC 5.1.2 にアップグレードする際の注意点のメモです。

 

まず、[ASP.NET MVC 4 Web アプリケーション] を [空] で作成します。

image

 

[NuGet パッケージの管理] の [更新プログラム] で [すべて更新] をクリックします。

image

 

すると、処理の途中で失敗します。

image

 

これは、Microsoft.Net.Http.ja の新しいバージョンが提供されていないことが原因です。
したがって、ASP.NET MVC 5.1.2 にアップグレードするには、先に Microsoft.Net.Http.ja をアンインストールします。

image

この状態で NuGet パッケージを更新すれば、ASP.NET MVC 5.1.2 へのアップグレードが成功します。

 

ただし、TFS のビルド時に NuGet パッケージを復元するで示した方法で
NuGet パッケージをビルドサーバー側で復元するように設定している場合、ビルドが失敗してしまいます。

image

 

これはどうやら、復元時に

packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets

というファイルが存在していないことが原因のようです。
したがって、このファイルだけをチェックインすれば解決します。

image

 

バージョン情報
Visual Studio 2012
ASP.NET MVC 5.1.2

参照
Microsoft.Bcl.Build.targets causes project loading to fail

カテゴリー: .NET Framework. タグ: . Leave a Comment »