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

前回の静的コンテンツにも 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 を強制する
静的コンテンツへの要求を動的に処理する

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 »

Windows Azure Web サイトで SSL を強制する

(追記: この投稿の強化版として、静的コンテンツにも SSL を強制するという投稿を追加しました。)

HTTPS および SSL による Windows Azure Web サイト (WAWS) のセキュリティ保護で説明されている通り、
Windows Azure Web サイトでは、ドメインを azurewebsites.net から変更しなければ最初から SSL を利用できます。
つまり、Web サイトを作成した時点で HTTP、HTTPS プロトコルの両方が有効になっています。

以下では、Windows Azure Web サイトに ASP.NET MVC アプリケーションを配置する場合に、
HTTPS プロトコルでの通信を強制する方法を考えていきます。

これを実現するには、HTTP でアクセスされた場合はプロトコルを HTTPS に変更してリダイレクトさせます。
永続的にリダイレクトさせる場合はステータス コード 301 (Moved Permanently) を、
一時的にリダイレクトさせる場合はステータス コード 302 (Found) または 307 (Temporary Redirect) を返します。

ASP.NET MVC で横断的に処理を挟み込むには、カスタム フィルター属性を利用します。
ASP.NET MVC には RequireHttpsAttribute クラスが用意されており、
これを使えばリダイレクトさせることはできるのですが、ステータス コードは 302 が固定で使われます。

そこで 301 も使えるようにするため、FilterAttribute クラスを継承して、次のような属性クラスを作成します。
RequireHttpsAttribute クラスと同様に、IAuthorizationFilter インターフェイスを実装します。

RequireHttps2Attribute.cs


using System;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;

namespace SslRedirect
{
    public class RequireHttps2Attribute : FilterAttribute, IAuthorizationFilter
    {
        // true: 301, false: 302
        public bool IsPermanent { get; private set; }

        public RequireHttps2Attribute(bool isPermanent = false)
        {
            IsPermanent = isPermanent;
        }

        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null) throw new ArgumentNullException("filterContext");

            if (!filterContext.HttpContext.Request.IsSecureConnection)
            {
                var url = filterContext.HttpContext.Request.Url.AbsoluteUri;
                var secureUrl = Regex.Replace(url, @"^\w+(?=://)", Uri.UriSchemeHttps);

                filterContext.Result = new RedirectResult(secureUrl, IsPermanent);
            }
        }
    }
}


 

すべてのコントローラーに対してこのフィルターを有効にするには、FilterConfig.cs に設定を追加します。

App_Start\FilterConfig.cs


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

namespace SslRedirect
{
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new RequireHttps2Attribute(true));
        }
    }
}


クラスまたはメソッドに属性を指定すれば、コントローラーまたはアクション単位でフィルターを有効にすることもできます。


[RequireHttps2(true)]
public ActionResult Index()
{
    return View();
}


 

さて、Windows Azure Web サイトにアプリケーションを配置して、HTTP でアクセスします。
すると、HTTPS の URL にリダイレクトされます。

Windows Azure Web サイト

 

また、Cookie が HTTPS のときだけ送信されるようにするには、Web.config で requireSSL の値を true に設定します。


<system.web>
  <httpCookies httpOnlyCookies="true" requireSSL="true" />
</system.web>


個別に指定するには、HttpCookie.Secure プロパティの値を明示的に true に設定します。


public ActionResult Index()
{
    Response.SetCookie(new HttpCookie("key1", "value1") { HttpOnly = true, Secure = true });

    return View();
}


 

この投稿の強化版: 静的コンテンツにも SSL を強制する

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

参照
HTTPS および SSL による Windows Azure Web サイト (WAWS) のセキュリティ保護
RequireHttpsAttribute クラス
Understanding ASP.NET MVC Filters and Attributes