WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

ASP.NET と Roslyn コンパイラの問題

by WebSurfer 2016年5月10日 17:03

Roslyn というのは Visual Studio 2015 に採用された次世代コンパイラだそうです。

ASP.NET Web アプリの場合 Web サーバーで動的にコンパイルが行われるので Roslyn の採用によってそれがうまく行かないケース(例: Trust Level が Medium のホスティングサービスを利用している)があるということを書きます。

元は MSDN Forum の「ASP.NET MVCのサイトがサーバーで実行できない問題」という表題のスレッドでの話です。

Visual Studio のデフォルト設定では、先の記事「コンパイル済みアセンブリの保存場所」で書きましたように、Web サイトプロジェクトでは全てのファイルを、Web アプリケーションプロジェクトでも一部のファイルをサーバーで動的にアセンブリにコンパイルします。

上に紹介した MSDN Forum の記事は Roslyn を使って Web サーバーで動的にコンパイルするところで問題が出たという話です。要約すると以下の通りです。

  1. VS2015 で ASP.NET MVC 5 の Web アプリケーションプロジェクトを新規作成。
  2. Roslyn コンパイラ関係の NuGet が 2 つ自動的にインストールされる。
    Microsoft.CodeDom.Providers.DotNetCompilerPlatform
    Microsoft.Net.Compilers
  3. その際、Web アプリの bin フォルダに roslyn というサブフォルダが生成され、その中に csc.exe という Roslyn コンパイラが配置される。
  4. さらに Web アプリの web.config に <system.codedom> 要素が生成され、サーバー側での動的コンパイルに上記 3 の Roslyn コンパイラを使うよう設定される。(注:ASP.NET MVC の場合は View を動的にコンパイルします)
  5. 記事を書いた人の運用サーバーでは上記 3 のコンパイラの実行権限がなく View のコンパイルに失敗する。
  6. NuGet をアンインストールすれば上記 4 の設定がなくなって上記 3 のコンパイラを使わないので問題が起きない。(C:\Windows\Microsoft.NET\Framework\v4.0.30319 フォルダの csc.exe を使うと思われます・・・が未確認です)

対症療法的な解決策は上に書いた NuGet 2 つをアンインストールして Roslyn コンパイラを使わないということですが、Roslyn を使う場合は以下のいずれかの対応を取ることになります。

  1. Visual Studio 側で View を含めて全てのファイルをコンパイルしてアセンブリを作成しそれをデプロイする。サーバー側での動的コンパイルは不要にする。または、
  2. IIS で当該アプリをホストとしているワーカープロセスに bin/roslyn フォルダの csc.exe の実行権限を与える。

上記 1 ですが、Visual Studio のデフォルトではサーバー側でコンパイルする設定になっていますのでその変更が必要です。具体的には、MSDN ライブラリの記事 Advanced Precompile Settings Dialog Box の画像にあるように [Allow precompiled site to be updatable] にデフォルトでチェックが入っていますので、そのチェックを外します。

ただし、そうするとサーバーにデプロイした後で、View のみ(ASP.NET Web Forms アプリの場合は.aspx, .ascx のみ)差し替えるということができなくなってしまいます。(View をちょっとだけ変更しても、開発環境で全ファイルを再コンパイルして再発行することになります)

それが気に入らない場合は上記 2 の方法を取るということになりますが、そもそもが Web サーバーで .exe ファイルの実行が制限されていることからこの問題にぶつかることが多いようですので、難しいかもしれません。

Tags: ,

ASP.NET

Web API で AuthorizationFilter 実装

by WebSurfer 2016年4月10日 14:39

MSDN Forum の「ASP.net MVC5 WEB APIのキャンセル(特定のデータの返却)する方法はありますか?」という表題のスレッドでの話です。

[クライアント]⇒[閲覧用ページ]⇒[Web API]⇒[DB サーバー]

という構成で、(1) [Web API]が要求を受けたらそこで時間外か否かを判断し、(2) 時間外であれば[Web API]のアクションメソッドは実行せず、(3) [閲覧用ページ]に時間外メッセージと遷移先の URL を応答として返し、(4) [閲覧用ページ]は応答の内容をチェックして時間外であれば遷移先に指定された URL に JavScript で遷移する・・・という方法を考えてみます。

もちろん時間外でなければ、上記で、(2)[Web API]でアクションメソッドを実行し、(3) 通常の応答を[閲覧用ページ]に返し、(4)[閲覧用ページ]は応答を処理して表示するということになります。

それを実現するためには、[Web API]において以下のような AuthorizationFilterAttribute を継承したフィルターを作って、OnAuthorization メソッドを override し、そこでアクションメソッドが実行される前に時間外か否かをチェックし、時間外であればその旨メッセージを返すようにします。(以下のコードは既存のコードを流用したため日付の範囲で制限してます)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Web.Http.Filters;
using System.Web.Http.Controllers;

namespace WebApi1.Filters
{
  [AttributeUsage(
       AttributeTargets.Method | AttributeTargets.Class,
       Inherited = true, AllowMultiple = false)]
  public class TimeLimitAttribute : AuthorizationFilterAttribute
  {
    public TimeLimitAttribute(string dateBegin, string dateEnd)
    {
      this.Begin = dateBegin;
      this.End = dateEnd;
    }
        
    private DateTime _begin = DateTime.MinValue;
    private DateTime _end = DateTime.MaxValue;

    public string Begin
    {
      set
      {
        DateTime dateSet = DateTime.Parse(value);                
        if (dateSet >= this._end)
        {
          throw new ArgumentException("開始日設定エラー");
        }
        this._begin = dateSet;
      }            
    }

    public string End
    {
      set
      {
        DateTime dateSet = DateTime.Parse(value);
        if (dateSet <= this._begin)
        {
          throw new ArgumentException("終了日設定エラー");
        }
          this._end = dateSet;
      }
    }

    public override void OnAuthorization(
                      HttpActionContext actionContext)
    {
      if (actionContext == null)
      {
        throw new ArgumentNullException("context が null");
      }

      DateTime current = DateTime.Now;
      if (current < this._begin || current > this._end)
      {

        HttpResponseMessage response = 
            new HttpResponseMessage();
        response.Content = 
            new StringContent("時間外,<リダイレクト先 URL>");
        actionContext.Response = response;
      }

      base.OnAuthorization(actionContext);
    }        
  }
}

ASP.NET MVC 用と ASP.NET Web API 用とでは Filter は違うそうなので注意してください。詳しくは記事「Web API における操作ごとの制御 (Validation, 認証/権限, Exception 処理 など)」を見てください。

上のフィルター属性を ApiController のクラスに付与することで制限がかけられます。たとえば、以下のようにフィルター属性を付与すれば、2013/12/1 ~ 2013/12/31 以外の日時にアクセスした場合、"時間外,<リダイレクト先 URL>" という文字列が[Web API]から応答として返ってきます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebApi1.Models;

namespace WebApi1.Controllers
{
    [WebApi1.Filters.TimeLimit("2013/12/1", "2013/12/31")]
    public class HeroesController : ApiController
    {
        private List<Hero> heroes = new List<Hero> {
              new Hero {Id = 1, Name = "スーパーマン"},
              new Hero {Id = 2, Name = "バットマン"},
              new Hero {Id = 3, Name = "ウェブマトリクスマン"},
              new Hero {Id = 4, Name = "チャッカマン"},
              new Hero {Id = 5, Name = "スライムマン"}
          };
                
        public IEnumerable<Hero> Get()
        {
            return heroes;
        }

        public Hero Get(int id)
        {
            return heroes[id - 1];
        }

        //・・・中略・・・
    }
} 

それを[閲覧用ページ]で jQuery.Ajax + JavaScript を使って、例えば上のアクションメソッドの public IEnumerable<Hero> Get() を呼んだ場合は以下のようの処置すれば、時間外であれば[Web API]からの応答に含まれる <リダイレクト先 URL> にリダイレクトされます。

function apiHeroesGet() {
    $.ajax({
        type: "GET",
        url: "api/heroes",
        contentType: "application/json; charset=utf-8",
        success: function (data, textStatus, jqXHR) {
            if (typeof(data) == "string" && 
                data.indexOf("時間外") == 0) {
                // 時間外の場合はリダイレクト
                var results = data.split(",");
                window.location.href = results[1];
            }
            else {
                // 時間内の場合の処置(省略)
            }
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // エラーの場合の処置(省略)
        }
    });
}

Tags: ,

Web API

IP アドレスで SQL Server に接続

by WebSurfer 2016年3月18日 15:42

サーバー名の代わりに IP アドレスを使って SQL Server に接続に行くと、TCP/IP プロトコルを使って接続に行くようです。

SQL Server への接続

自分の開発マシンには SQL Server 2005 Developer Edition と SQL Server 2008 Express Edition がインストールしてあって、プロトコルは前者が共有メモリのみ有効、後者が共有メモリ、名前つきパイプ、TCP/IP(ポートは 1433)が有効になっています。

そこに、コマンドラインから sqlcmd を使って接続に行くと、(local) の場合は SQL Server 2005 に、IP アドレスの場合は SQL Server 2008 に接続されます。(SELECT @@version; クエリで確認しました)

そして、上の画像で示したとおり、(local) の場合はプロトコルは共有メモリで、IP アドレス(192.168.1.4)の場合は TCP/IP で接続に行きます。

TCP/IP プロトコルを有効にしてあるのは SQL Server 2008 Express のみなので、TCP/IP プロトコルで接続に行く場合、自動的に SQL Server 2008 Express に接続されるということのようです。

知ってました? 実は自分は知らなくて、(local) とかサーバー名を使った場合と同様に SQL Server 2005 Developer Edition に接続に行くと思っていて、半日ぐらいハマってしまいました。(汗)

その他いろいろ新発見があったので、覚えておいたほうがよさそうなことを備忘録として以下に書いておきます。

サーバー名でなく IP アドレスを使った場合、プロトコルを強制的に指定しようとしても、指定できるプロトコルは tcp のみで、np, lpc ではエラーとなって接続できません。実際に試してみたところ、以下の結果となりました。

  • sqlcmd -S tcp:192.168.1.4 -E ⇒ OK
  • sqlcmd -S np:192.168.1.4 -E ⇒ NG
    エラーメッセージ:Named Pipes Provider: Could not open a connection to SQL Server [64].
  • sqlcmd -S lpc:192.168.1.4 -E ⇒ NG
    エラーメッセージ:SQL Server Network Interfaces: Cannot open a Shared Memory connection to a remote SQL Server instance [87].

sqlcmd -S (local) -E または sqlcmd -S <サーバー名> -E は SQL Server 2005 に共有メモリを使って接続されます。

sqlcmd -S (local)\sqlexpress -E または sqlcmd -S <サーバー名>\sqlexpress -E は SQL Server 2008 Express に共有メモリを使って接続されます。

インスタンス名を追加して sqlcmd -S 192.168.1.4\SQLEXPERESS -E とすると "SQL Server Network Interfaces: Error Locating Server/Instance Specified [xFFFFFFFF]." というエラーとなって接続に失敗します。sqlcmd -S 192.168.1.4\SQLEXPERESS,1433 -E というようにポートを指定すれば接続できます。

その理由は、先の記事 SQLEXPRESS は「名前つきインスタンス」名で書きましたように、インスタンス名を指定した場合、UDP 1434 (SQL Browser サービス) に接続して、指定したインスタンス名のポート番号を取得し、その後対象ポートに接続をするという動作になるからのようです。

MSDN Blog の記事 Troubleshooting Connectivity #1 – SQL Server への接続によると、SQL Server に接続する場合、以下の 3 つのステップにで処理が行われるそうです。この記事の話は「OS レベルのセッション確立」のステップに該当します。

  1. OS レベルのセッション確立
  2. ログイン認証
  3. データベースアクセス

上に紹介した MSDN Blog の記事にはいろいろ参考になることが書いてありますので、一読されることをお勧めします。あと、Troubleshooting Connectivity #5 – セッション確立までの動作という記事も参考になると思います。

Tags: ,

SQL Server

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar