WebSurfer's Home

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

inc ファイルの C# コードにブレークポイント

by WebSurfer 2018年1月22日 19:02
ASP.NET で使用する .inc ファイルの C# コードにブレークポイントを設定するにはどうすればいいかを書きます。(そもそも .inc ファイルに C# のコードを書く必要があるのかという話はちょっと置いときます)

inc ファイルのコードにブレークポイント

元の話は terateil のスレッド ASP.NETのデバッグについてです。

C# のコードを .inc ファイルに含めて、コードビハインドの .aspx.cs ファイルの C# のコードと同様に ASP.NET にコンパイルさせることができます。

ただし、少なくとも自分の環境では、デフォルトではブレークポイントを設定できませんでした。クリックして特定の行にブレークポイントを設定しようとすると <script runat="server"> の行に設定されるように見えますが、デバッグ実行で止まりません。

どうすればよいかと言うと、Visual Studio の設定で[ツール(T)]⇒[オプション(O)...]⇒[テキストエディタ]のファイル拡張子に inc を追加してやります。

テキストエディター、ファイル拡張子の設定

Visual Studio のヘルプによると、この設定で "特定の拡張子を持つすべてのファイルを Visual Studio 統合開発環境 (IDE) で扱うように指定できます" とのことです。文字のハイライトやインテリセンスが働くようになるだけでなく、ブレークポイントの設定も可能になります。(自分の環境では、.inc ファイルをいったん閉じて再度開く必要がありましたが)

知ってました? 実は、自分は teratail のスレッドで聞くまで知らなかったです。もっと正直に言うと、C# のコードを .inc ファイルに含めても、リテラル扱いとなって、生の C# のコードがそのまま html ソースに出力されると思ってました。(汗)

以下に、検証に使ったコードと、若干のコメント・注意点を書いておきます。

.inc ファイル

この例では JavaScript のコードと C# のコードの両方を含めました。JavaScript のコードは、インラインで .aspx ファイルに書き込んだのと同様にリテラルとして取り込まれ、そのまま html ソースに書き出される。一方、C# のコードはコードビハインド扱いとなり、.aspx.cs ファイル の C# のコードと同様にコンパイルされます。

System.Diagnostics.Debugger.Break(); はブレークポイントが設定できないと思ったので代わりに置いたものです。不要となったのでコメントアウトしました。

<script type="text/javascript">
    var msg = "message";
    // alert(msg);
</script>

<script runat="server">
    protected void Selection_Change(Object sender, EventArgs e)
    {
        //System.Diagnostics.Debugger.Break();
        
        DropDownList ddl = (DropDownList)sender;

        Label1.Text = ddl.SelectedValue;
        
        int i;
        for (i = 0; i < 10; i++)
        {
            //System.Diagnostics.Debugger.Break();
        }
        
        Label1.Text += ", i = " + i.ToString();
    }
</script>

.aspx.cs ファイル

コードビハインドとして Visual Studio が自動生成した C# のコードです。自動生成されたコードのそのままで手は加えていません。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _0023_includeFile : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        
    }
}

.aspx ファイル

<!-- #include file ="analytics.inc" --> で上の .inc ファイルを取り込みます。本体には DropDownList と Label を配置。DropDownList の SelectedIndexChanged イベントのハンドラは .inc ファイルの C# のコードに定義しました。

ASP.NET がこのコードをコンパイルする際、.inc ファイルの JavaScript のコードはそのままリテラルとして取り込まれます(結果、html ソースにはその位置にそのまま JavaScript のコードが書き出されます)。.inc ファイルと .aspx.cs ファイルの C# のコードはコンパイルされます。

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="0023-includeFile.aspx.cs" 
    Inherits="_0023_includeFile" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <!-- #include file ="analytics.inc" -->
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:DropDownList id="ColorList"
      AutoPostBack="True"
      OnSelectedIndexChanged="Selection_Change"
      runat="server">
      <asp:ListItem Selected="True" Value="White">
        White
      </asp:ListItem>
      <asp:ListItem Value="Silver">
        Silver
      </asp:ListItem>
      <asp:ListItem Value="DarkGray">
        Dark Gray
      </asp:ListItem>
      <asp:ListItem Value="Khaki">
        Khaki
      </asp:ListItem>
      <asp:ListItem Value="DarkKhaki">
        Dark Khaki
      </asp:ListItem>
    </asp:DropDownList>
    <br />
    <asp:Label ID="Label1" runat="server" />
  </div>
  </form>
</body>
</html>

Tags: ,

ASP.NET

異なるデータソースの結合と表示

by WebSurfer 2017年11月26日 13:34

異なるデータソース(例えば SQL Server と CSV ファイル)のレコードを内部結合または左外部結合して GridView などに一覧表示する例を書きます。

GridView に表示

上の画像はこの記事で紹介するサンプルの実行結果で、SQL Server と CSV ファイルをデータソースに使い、左側が内部結合、右側が左外部結合した結果一覧を ASP.NET Web Forms アプリの GridView に表示したものです。

この記事で使用したデータソースは、Microsoft が提供しているサンプルデータベース Northwind の Orders テーブルと、Customers テーブルから一部のフィールド / レコードを抜き出して作った以下の画像の CSV ファイルです。

CSV ファイル内容

データソースが両方とも SQL Server のサンプルデータベース Northwind にあれば、SELECT クエリで JOIN 句を使って結合し、その結果を DataTable などに取得するのが簡単ですが、一方が CSV ファイルではそうはいきません。

ではどうするかと言うと、SQL Server のテーブルと CSV ファイルそれぞれから List<T> 型のオブジェクトを作り、それを Linq で結合した結果を GridView のデータソースとしてバインドしてやるのがよさそうです。

Linq を使って結合する例は Microsoft の文書「join 句 (C# リファレンス)」やそれからリンクが張ってある記事が参考になりました。

上の画像を表示したサンプルコードは以下の通りです。説明はコメントとして書きましたので、それを見てください。(手抜きでスミマセン)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;

// SQL Server の Northwind サンプルデータベース Orders
// テーブルのレコードを格納するクラス定義
public class Order
{
    public int OrderID { get; set; }
    public string CustomerID { get; set; }
    public int& EmployeeID { get; set; }
    public DateTime& OrderDate { get; set; }
    public DateTime& RequiredDate { get; set; }
    public DateTime& ShippedDate { get; set; }
    public int& ShipVia { get; set; }
    public decimal& Freight { get; set; }
    public string ShipName { get; set; }
    public string ShipAddress { get; set; }
    public string ShipCity { get; set; }
    public string ShipRegion { get; set; }
    public string ShipPostalCode { get; set; }
    public string ShipCountry { get; set; }
}

// CSV ファイルのレコードを格納するためのクラス定義
public class Customer
{
    public string CustomerID { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string ContactTitle { get; set; }
}

// 結合後の結果を格納するためのクラス定義
public class Result
{
    public int OrderID { get; set; }
    public string CompanyName { get; set; }
    public DateTime& OrderDate { get; set; }
    public decimal& Freight { get; set; }
}

public partial class _0019_GridViewJoinedList : 
    System.Web.UI.Page
{
  // SQL Server のサンプルデータベース Northwind の
  // Orders テーブルからデータを取得して List<Order>
  // オブジェクトを生成。Entity Framework を使う方が簡単
  // だが、ここではプリミティブに ADO.NET の SqlDataReader 
  // を使用した。
  protected List<Order> CreateOrderList()
  {
    List<Order> orders = new List<Order>();

    string connString = WebConfigurationManager.
            ConnectionStrings["NORTHWINDConnectionString"].
            ConnectionString;

    string query = "SELECT [OrderID], [CustomerID]," +
            "[EmployeeID], [OrderDate], [RequiredDate]," +
            "[ShippedDate], [ShipVia], [Freight]," +
            "[ShipName], [ShipAddress], [ShipCity]," +
            "[ShipRegion], [ShipPostalCode], [shipCountry]" +
            "FROM [Orders]";

    using (SqlConnection conn = new SqlConnection(connString))
    {
      conn.Open();
      using (SqlCommand cmd = new SqlCommand(query, conn))
      {
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
          if (reader != null)
          {
            while (reader.Read())
            {
              Order record = new Order();

              record.OrderID = reader.GetInt32(0);
              record.CustomerID = reader.IsDBNull(1) ?
                      null : reader.GetString(1);
              record.EmployeeID = reader.IsDBNull(2) ?
                      null : (int&)reader.GetInt32(2);
              record.OrderDate = reader.IsDBNull(3) ?
                      null : (DateTime&)reader.GetDateTime(3);
              record.RequiredDate = reader.IsDBNull(4) ?
                      null : (DateTime&)reader.GetDateTime(4);
              record.ShippedDate = reader.IsDBNull(5) ?
                      null : (DateTime&)reader.GetDateTime(5);
              record.ShipVia = reader.IsDBNull(6) ?
                      null : (int&)reader.GetInt32(6);
              record.Freight = reader.IsDBNull(7) ?
                      null : (decimal&)reader.GetDecimal(7);
              record.ShipName = reader.IsDBNull(8) ?
                      null : reader.GetString(8);
              record.ShipAddress = reader.IsDBNull(9) ?
                      null : reader.GetString(9);
              record.ShipCity = reader.IsDBNull(10) ?
                      null : reader.GetString(10);
              record.ShipRegion = reader.IsDBNull(11) ?
                      null : reader.GetString(11);
              record.ShipPostalCode = reader.IsDBNull(12) ?
                      null : reader.GetString(12);
              record.ShipCountry = reader.IsDBNull(13) ?
                      null : reader.GetString(13);

              orders.Add(record);
            }
          }
        }
      }
    }
    return orders;
  }

  // CSV ファイルからデータを取得して List<Customer> オブ
  // ジェクトを生成。
  protected List<Customer> CreateCustomerList()
  {
    List<Customer> customers = new List<Customer>();

    string csvFile = Server.MapPath("~/App_Data/TextFile.csv");

    using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp =
      new Microsoft.VisualBasic.FileIO.TextFieldParser(
        csvFile,
        System.Text.Encoding.GetEncoding("Shift_JIS")))
    {
      //フィールドがデリミタで区切られている
      tfp.TextFieldType =
        Microsoft.VisualBasic.FileIO.FieldType.Delimited;
      // デリミタを , とする
      tfp.Delimiters = new string[] { "," };
      // フィールドを " で囲み、改行文字、デリミタを
      // 含めることができるか
      tfp.HasFieldsEnclosedInQuotes = true;
      // フィールドの前後からスペースを削除
      tfp.TrimWhiteSpace = true;

      while (!tfp.EndOfData)
      {
        string[] fields = tfp.ReadFields();

        Customer customer = new Customer()
        {
          CustomerID = fields[0],
          CompanyName = fields[1],
          ContactName = fields[2],
          ContactTitle = fields[3]
        };
        customers.Add(customer);
      }
    }
    return customers;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!IsPostBack)
    {
      List<Order> orders = CreateOrderList();
      List<Customer> customers = CreateCustomerList();

      // 内部結合
      var innerJoin = from o in orders
                      join c in customers
                      on o.CustomerID equals c.CustomerID
                      select new Result
                      {
                        OrderID = o.OrderID,
                        CompanyName = c.CompanyName,
                        OrderDate = o.OrderDate,
                        Freight = o.Freight
                      };

      // シーケンスが空の場合に返すデフォルト値
      // 下の DefaultIfEmpty メソッドの引数に設定する
      Customer defaultValue = new Customer() {
                CustomerID = string.Empty,
                CompanyName = string.Empty,
                ContactName = string.Empty,
                ContactTitle = string.Empty };

      // 左外部結合
      var leftOuterJoin = 
          from o in orders
          join c in customers
          on o.CustomerID equals c.CustomerID into cGroup
          from item in cGroup.DefaultIfEmpty(defaultValue)
          select new Result
          {
              OrderID = o.OrderID,
              CompanyName = item.CompanyName,
              OrderDate = o.OrderDate,
              Freight = o.Freight
          };

      // 上の画像の左側の GridView(内部結合)
      GridView1.DataSource = innerJoin;
      GridView1.DataBind();

      // 上の画像の右側の GridView(左外部結合)
      GridView2.DataSource = leftOuterJoin;
      GridView2.DataBind();
    }
  }
}

Tags: , ,

ASP.NET

IE11 と Referer

by WebSurfer 2017年8月13日 17:02

ASP.NET 4 の Web Forms アプリで、クエリ文字列に日本語を含めて要求をかけた場合、Windows 10 の IE11 と Edge ではポストバックの際 Referer が送信されないという話を書きます。(ASP.NET 4.5 では問題は解消されています。ASP.NET 3.5 以下は未確認です)

Fiddler によるキャプチャ

そもそもの原因は Windows 10 の IE11 や Edge ではなく、ASP.NET 4(3.5 以前も?・・・未確認です)が応答の form 要素に設定する action 属性にあるようです。

例えば、IE11 のアドレスバーに直接以下の URL(クエリ文字列は "日本語" を UTF-8 のパーセントエンコードしたものです)を入力して直接ページを GET 要求したとします。

http://aspnet4site/test.aspx?P1=%e6%97%a5%e6%9c%ac%e8%aa%9e

そうすると、ASP.NET により応答の form 要素の action 属性に設定される URL は以下のようになります。

action="./test.aspx?P1=%u65e5%u672c%u8a9e"

つまり、要求 URL のクエリ文字列の "日本語" は UTF-8 のパーセントエンコーディングなのに、応答の form 要素の action 属性に設定されるのは Unicode のパーセントエンコーディングになってしまいます。そこに問題があるようです。

具体的には下にアップした簡単なサンプルのページ test.aspx で再現できます。

上の画像は、UTF-8 でパーセントエンコーディングした "日本語" をクエリ文字列に含めた URL を IE11 のアドレスバーに直接入力して test.aspx を要求し、表示されたページで 2 回ポストバックを行ったときの Fiddler によるキャプチャ画像です。

上の画像の #1 が初回の要求・応答で、#3 が 1 回目のポストバック、#4 が 2 回目のポストバックの際の要求・応答です。

問題は 2 回目のポストバックのときで、ブラウザからリファラが送信されません。Edge も同様で 2 回目のポストバックの際はリファラは送信されません。

どのような動きになるかと言うと・・・

#1 の初回要求に対して ASP.NET が返す応答の form 要素の action 属性に同じページの URL が設定されますが、URL に含まれるクエリ文字列の "日本語" は Unicode のパーセントエンコーディング %u65e5%u672c%u8a9e となってしまいます。

なので、1 回目のポストバックの時は要求 URL に含まれるクエリ文字列は %u65e5%u672c%u8a9e となります。上の画像の #3 を見てください。

ただし、1 回目のポストバックの際にブラウザから送信されるリファラは、最初の要求の URL(すなわちアドレスバーに直接入力した URL)になります。つまり、クエリ文字列の "日本語は" UTF-8 のパーセントエンコーディングなので問題なくリファラとして送信されます。問題は 2 回目以降です。

2 回目のポストバックの時(上の画像の #4)は、1 回目のポストバックの際の要求 URL(すなわちクエリ文字列の "日本語" が %u65e5%u672c%u8a9e)をリファラとして送信するはずですが、Windows 10 の IE11 と Edge では送信されません。3 回目、4 回目も同様です。

Microsoft の公式文書が見つけられないので想像ですが、Windows 10 の IE11 と Edge ではセキュリティ対策が強化され、URL に UTF-8 として解釈できないコードがあるとリファラを送信しないということではないかと思っています。

ちなみに、Windows 7 の IE11 では %u65e5%u672c%u8a9e となってしまっていてもリファラは送信されるそうです(聞いた話で未検証・未確認)。Chrome 60.0.3112.90, Firefox 54.0.1 では %u65e5%u672c%u8a9e でも送信されることは確認しました。

やはり、URL にはクエリ文字列を含めて ASCII 文字のみ使用することで徹底するのがよさそうです。

サンプルページ test.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.UrlReferrer != null)
        {
            Label1.Text = Request.UrlReferrer.ToString();
        }
        else
        {
            Label1.Text = "無し";
        }
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <h1>Page B</h1>
    <asp:Button ID="Button1" runat="server" Text="Button" />
    <br />
    UrlReferrer: 
    <asp:Label ID="Label1" runat="server"></asp:Label>
    </form>
</body>
</html>

Tags: , ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2018年2月  >>
28293031123
45678910
11121314151617
18192021222324
25262728123
45678910

View posts in large calendar