複数のページからPostBackUrlで遷移したい時

この記事は、One ASP.NET Advent Calendar 2013の22日目の記事です。

前回はTAKANO Sho / @masaru_b_clさんのASP.NET Friendly URLsを使う #aspnetjp by @masaru_b_clでした。

で、怒涛の連続投稿ですが全てはこの記事のための伏線でした。

前回はPostBackUrlでの値の受け渡しについて紹介しましたが、今回は複数のページからPostBackUrlで遷移したときの値の取得の仕方についてです。

PreviousPageTypeは複数指定できない

遷移先のaspxにPreviousPageTypeを2つ追加してもコンパイルエラーは起きません。

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Result.aspx.vb" Inherits="PostBackUrlMulti.Result" %>

<%@ PreviousPageType VirtualPath="~/Input1.aspx" %>
<%@ PreviousPageType VirtualPath="~/Input2.aspx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
            <asp:Label ID="LblResult" runat="server" />
        </form>
    </body>
</html>

が、実際に起動してみて遷移先のページに遷移するとエラーになります。

PreviousPageTypeは一つしか指定できないようです。

そもそもaspxに2つPreviousPageTypeを書いても、designerファイルには1つしか登録されていません。

Public Shadows ReadOnly Property PreviousPage() As PostBackUrlMulti.Input2
    Get
        Return CType(MyBase.PreviousPage, PostBackUrlMulti.Input2)
    End Get
End Property

後に書いたほうで上書きされるようです。

解決策

遷移先の画面でインターフェースを決定してしまえば、複数の画面からPostBackUrlで遷移してきても対応できます。

まずは下記のようにInterfaceを作成します。

Public Interface IResult

    ''' <summary>
    ''' 受け渡しする値を格納するプロパティ
    ''' </summary>
    ''' <remarks></remarks>
    ReadOnly Property TxtInputValue As String

End Interface

次に遷移元となるページで上記のInterfaceを実装し、プロパティに遷移先に渡したい値をセットします。

Public Class Input1
    Inherits System.Web.UI.Page
    Implements IResult

    ''' <summary>
    ''' Interfaceの実装時にできたプロパティ。
    ''' PostBackUrlで遷移したページに値を渡す。
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property TxtInputValue As String Implements IResult.TxtInputValue
        Get
            Return Me.TxtInput.Text
        End Get
    End Property

End Class
Public Class Input2
    Inherits System.Web.UI.Page
    Implements IResult

    ''' <summary>
    ''' Interfaceの実装時にできたプロパティ。
    ''' PostBackUrlで遷移したページに値を渡す。
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property TxtInputValue As String Implements IResult.TxtInputValue
        Get
            Return Me.TxtInput.Text
        End Get
    End Property

End Class

遷移先のページではaspxからPreviousPageTypeを削除し、PageクラスのPreviousPageをInterfaceにキャストして返すようにプロパティを追加します。

Public Class Result
    Inherits System.Web.UI.Page

    ''' <summary>
    ''' 遷移元のページ
    ''' </summary>
    ''' <remarks></remarks>
    Public Shadows ReadOnly Property PreviousPage() As IResult
        Get
            Return CType(MyBase.PreviousPage, IResult)
        End Get
    End Property

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        ' 遷移元のページの値を表示する
        Me.LblResult.Text = PreviousPage.TxtInputValue
    End Sub

End Class

こうすることでInput1.aspxとInput2.aspxからPostBackUrlで遷移したときに、両方のページから値を取得することが出来ます。

ただ、Interfaceを実装していないページから遷移してきたときはエラーになるのでCTypeでキャストせずにTryCastでキャスト後、Nothingならほげもげ、という処理を書く必要があります。

そもそもPostBackUrlでの遷移自体あまりオススメされてないようなので、POSTでページ遷移したいならおとなしくASP.NET MVCを導入したほうがいいかもしれません。

まとめ

ということでもう誰も使ってないんじゃないかというくらいWeb Formsのレガシーな記事でしたが、もし来年もOne ASP.NET Advent Calendarがあるようならば新しめな技術の記事を書きたいですなー、と思います。

ということで明日は謎社CTOのneueccさんがブッチ切りの最新技術について書いてくれると思いますよ!!

乞うご期待!!

カテゴリー: .NET, ASP.NET, VB, Web Forms | コメントする

PostBackUrlで遷移元のページの値を取得する

前回、ASP.NET Web Formsでのページ遷移する方法を紹介しました。

今回は前回の最後に紹介したPostBackUrlでページ遷移したときに、遷移元のページの値を取得する方法について紹介します。

遷移元ページの設定

遷移元のページでは、遷移先のページに渡したい値をプロパティとして設定しておきます。

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Input.aspx.vb" Inherits="PostBackUrlSample.Input" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>遷移元ページ</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <asp:TextBox ID="TxtInput" runat="server" />
            <asp:Button ID="BtnPost" runat="server" Text="送信" PostBackUrl="~/Result.aspx" />
        </form>
    </body>
</html>
Public ReadOnly Property TxtInputValue As String
    Get
        Return Me.TxtInput.Text
    End Get
End Property

上記ではaspx上に配置したTextBoxコントロールのTextプロパティをページのプロパティとして設定しています。

特に読み取り専用にする必要もないですが、遷移先のページで上書きしても意味ないので読み取り専用にしておくのが無難かと思います。

遷移先ページの設定

遷移先のページではPreviousPageTypeとして遷移元のページを登録しておきます。
(下記のaspxの3行目です。)

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Result.aspx.vb" Inherits="PostBackUrlSample.Result" %>

<%@ PreviousPageType VirtualPath="~/Input.aspx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>遷移先のページ</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <asp:Label ID="LblResult" runat="server" />
        </form>
    </body>
</html>
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Me.LblResult.Text = PreviousPage.TxtInputValue
End Sub

PreviousPageTypeを指定すると、該当する遷移元のページのクラスがPreviousPageとしてdesignerファイルに登録されます。

Public Shadows ReadOnly Property PreviousPage() As PostBackUrlSample.Input
    Get
        Return CType(MyBase.PreviousPage, PostBackUrlSample.Input)
    End Get
End Property

こうすることで遷移元で公開しているプロパティにアクセスすることができます。

実行してみると遷移元のページの値が遷移先のページに表示されることがわかると思います。

まとめ

遷移元では次のページに渡したい値をプロパティとして設定し、遷移先のページでは前のページをPreviousPageTypeとして登録して設定されたプロパティを通じて値を取得します。

上記のソースではInput.aspx以外からResult.aspxに遷移すると、Input.aspxのコードビハインドのオブジェクトが生成されていないためエラーになります。

Input.aspx以外のページからも遷移する場合は何らかの対応が必要になるので注意が必要です。

ちなみにViewStateを無効化しても上記のソースは動きますよ!

カテゴリー: .NET, ASP.NET, VB, Web Forms | コメントする

ASP.NET Web Formsでのページ遷移

よく目にするテーマですけど、改めておさらいと言うことで。

今回はあくまでページ遷移の方法を載せるだけで、ページ間の値の保持については言及しません。

なぜよくテーマになるのか

ASP.NET Web Formsは基本、1ページ内で処理が完結するフレームワークです。
(いわゆるポストバックってやつです。)

そのため普通にWeb Formsを使って開発をしていると他ページへの遷移方法でつまづくことがあるかもしれません。

ページ遷移の方法

早速ですがページ遷移の方法を紹介します。

今回は以下の4つの方法についてです。

① HyperLinkコントロールのNavigateUrl

② Response.Redirect

③ Server.Transfer

④ IButtonコントロールのPostBackUrl

その1 HyperLinkコントロールのNavigateUrl

一つ目はHyperLinkコントロールのNavigateUrlです。

HyperLinkコントロールはHTMLとして出力されたときに<a>タグとして吐き出されます。

NaviagteUrlで指定した遷移先は<a>タグ内のhref要素として出力されます。

<asp:HyperLink NavigateUrl="~/Account/Login.aspx" runat="server" Text="ログインに遷移" />

上記のように記述した場合、下記のようにHTMLとして出力されます。

<a href="Account/Login.aspx">ログインに遷移</a>

NavigateUrlは同一プロジェクト、サイト内への遷移であれば相対パスで指定できます。

外部サイトへのリンクとして利用する場合はそのURLをそのまま記述すればOKです。

また、動的に遷移先を変えたい場合はコードビハインド側でNavigateUrlを指定します。

Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    ' フラグが渡ってきたらTOPページに遷移する
    Me.HypTransfer.NavigateUrl = If(Me.Request.QueryString("flg") = "1", "Index.aspx", "~Account/Login.aspx")
End Sub

その2 Response.Redirect

二つ目はResponse.Redirectを使用した遷移です。

Private Sub BtnRedirect_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnRedirect.Click
    Response.Redirect("~/Account/Login.aspx")
End Sub

上記のコードはボタンが押下されたときに別ページへ遷移する場合の例です。

ポストバックでResponse.Redirectでページ遷移する場合、2回サーバーにアクセスすることになります。

まずポストバックのリクエストで1回、次に遷移先のページへのリクエストで2回です。

サーバーアクセスを減らしたいのであれば別の方法でページ遷移を実装すべきかもしれません。

また、Response.Redirectの第2引数のendRequestで、以降の処理を中断するかどうかを指定することができます。

endRequestをTrueにすると後続処理は行われませんし、Falseにすれば後続処理は実施されます。

endRequestを指定しない場合はTrueにしたときと同じ挙動になります。

Private Sub BtnRedirect_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnRedirect.Click
    Response.Redirect("~/Account/Login.aspx")
    Session("Key") = "session" ' ここの処理は行われない
End Sub
Private Sub BtnRedirect_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnRedirect.Click
    Response.Redirect("~/Account/Login.aspx", True)
    Session("Key") = "session" ' ここの処理は行われない
End Sub
Private Sub BtnRedirect_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnRedirect.Click
    Response.Redirect("~/Account/Login.aspx", False)
    Session("Key") = "session" ' ここの処理は行われる
End Sub

また、Response.RedirectはHyperLinkのNavigateUrlと同様に外部サイトへ遷移することも可能です。

その3 Server.Transfer

三つ目はServer.Transferでの遷移です。

Private Sub BtnTransfer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnTransfer.Click
    Server.Transfer("~/Account/Login.aspx")
End Sub

上記のコードもResponse.Redirectと同様、ボタンが押下されたときに別ページへ遷移する場合の例です。

ただResponse.Redirectがページ遷移するためにサーバーに2回アクセスしますが、Server.Transferは1回のアクセスで済みます。

というのもResponse.Redirectが遷移前のページにポストバックした後に再度新しいページへのリクエストが送信されるのに対し、
Server.Transferは遷移前のページにポストバックしている時にサーバー内で別ページへ遷移するためです。

そのため遷移後のURLは遷移前のページのURLとなります。

もう一つの違いは上記の通りServer.Transferはサーバー内でページ遷移するため、外部サイトへの遷移ができません。

またResponse.Redirectと違って遷移前のページのリクエスト情報を引き継ぐことができますが、ここでは詳細は割愛します。

その4 IButtonコントロールのPostBackUrl

IButtonコントロールと聞いてもピンとこないかもしれませんが、Button、ImageButton、LinkButtonコントロールのことです。

これらのコントロールはsubmitボタンとしてHTMLに出力されます。

通常ASP.NET Web FormsでIButtonコントロールを押下すると自身のページにポストバックしますが、PostBackUrlを指定すればポストバックするページを変更することができます。

<asp:Button ID="BtnPostBackUrl" runat="server" Text="他ページへポストバック" PostBackUrl="~/Account/Login.aspx" />

PostBackUrlでポストバック先を指定した場合、遷移元でのポストバックは発生せずに遷移先のページへ遷移します。

外部サイトへPOSTして遷移することも可能ですが、使用することはまずないと思います。

まとめ

以上ASP.NET Web Formsでページ遷移する方法を4つ紹介しました。

色々とクセのあるフレームワークで上記の4つでも思わぬ不具合が発生することがあると思います。

最初に述べた通り、1ページ内で処理が完結することを目的として作られたフレームワークなので、ページ遷移を頻繁に行うアプリであればASP.NET MVCなど別のフレームワークを使用すべきでしょう。

それでもWeb Formsを使いたいのであれば上記4つの方法から試行錯誤してベストプラクティスを発見する必要があります。

カテゴリー: .NET, ASP.NET, VB, Web Forms | コメントする

イベントハンドラの修飾子はprivateか?protectedか?

前々から気になっていたんだけど、業務中にたまたま発見しました。

VBではどうなのか

ASP.NET Web Formの開発でコードビハインドをVBで書いている方は、
Visual Studioのエディタの上の方にあるドロップダウンから選択して、
イベントを追加している方もいらっしゃるのではないでしょうか?

その時に追加されるイベントメソッドの修飾子はprivateになっています。
(少なくともVS2010と2012はそうなるはずです。)

しかし、aspxファイルを追加したときに既に追加されているPageのLoadイベントは、
protectedになっているんですね。

前にも気になって全部のイベントメソッドの修飾子をprivateにしたりprotectedにしたり、
色々と弄ってみたんですが両方とも問題なく動きました。

C#ではどうなのか

C#だとVBのようなイベントの追加は出来ず、イベントを追加しようと思ったら、
コードビハインド側に適切なEventArgsを引数にしたメソッドを追加して、
それをaspx側でコントロールのOnXxx(On + イベント名)プロパティで指定してやらなければいけません。
(Pageのイベントは例外ですが、これについては後述します。)

ただ、この時aspx側で指定するメソッドはprotectedでなくprivateにすると、
実行時にエラーになります。

なぜVBはprivateで良くてC#ではダメなのか

コートビハインドをVBで書いたときとC#で書いたときの違いに、
イベントの紐付け方の違いがあります。

VBではHandles句というものがあり、イベントのメソッド名の後にHandles句を書くことで、
コードビハインド側だけでコントロールとイベントの紐付けを行うことができます。

Private Sub Button_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Button.Click
    ' イベント処理
End Sub

しかしC#にはHandles句がないため先に述べたような方法でイベントを紐付ける必要があります。

・aspx.cs側

protected void Button_Click(object sender, EventArgs e)
{
    // イベント処理
}

・aspx側

<asp:Button ID="Button" runat="server" OnClick="Button_Click" />

で、なぜこのときにprivateではいけないのかというと、
aspxはコードビハインド側のクラスを継承しています。

aspxファイルの一番最初に書いてありますね。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm.aspx.cs" Inherits="WebApp.WebForm" %>

上記ではaspxがWebFormというコードビハインドが書かれたクラスの子クラスにあたるため、
コードビハインドに書かれたメソッドを参照するには修飾子がprotected以上でないとダメなんですね。

VBでもC#と同様にHandles句を使わずにaspxでイベントの紐付けが出来ますが、
この場合はやはり修飾子はprotectedでなければなりません。

C#でのPageイベント

C#でPageのイベントのバインドを自動的に行うかどうかは、
aspxのAutoEventWireupプロパティに依存します。
(ひとつ上のソースを参照して下さい。)

このプロパティがtrueの場合は勝手にイベントを紐付けてくれますが、
falseにした時は下記のようにコードビハインドクラスのコンストラクターで、
明示的に紐付けなければなりません。

public partial class WebForm : System.Web.UI.Page
{

    /// <summary>
    /// コンストラクター
    /// </summary>
    public WebForm()
    {
        this.Init += new EventHandler(this.Page_Init);
        this.Load += new EventHandler(this.Page_Load);
    }

    /// <summary>
    /// Page_Initイベント
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Page_Init(object sender, EventArgs e)
    {
    }

    /// <summary>
    /// Page_Loadイベント
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Page_Load(object sender, EventArgs e)
    {
    }

}

この時はaspx側がコードビハインド側のメソッドを参照することはないので、
イベントの修飾子はprivateでも大丈夫です。

ちなみにVBの場合はPageのイベントもHandles句で紐付けてくれてますね。

C#でもコードビハインドだけでイベント紐付け

実はC#でもコードビハインドでイベントの紐付けが出来ます。

この場合は以下のようにPageのInitイベントでイベントを付与します。

/// <summary>
/// Page_Initイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Page_Init(object sender, EventArgs e)
{
    this.Button.Click += new EventHandler(this.Button_Click);
    this.DropDownList.SelectedIndexChanged += new EventHandler(this.DropDownList_SelectedIndexChanged);
}

/// <summary>
/// Button_Clickイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Button_Click(object sender, EventArgs e)
{
}

/// <summary>
/// DropDownList_SelectedIndexChangedイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void DropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
}

この場合もイベントメソッドの修飾子はprivateでも大丈夫です。

結論

VBの場合

イベントの紐付けを…
・Handles句を使用するならprivateでOK
・aspxで紐付けるならprotectedでないとNG

C#の場合

イベントの紐付けを…
・Page_Initイベントで行うならprivateでOK
・aspxで紐付けるならprotectedでないとNG

こんなカンジですかね。

何で今まで気付かなかったのか不思議なんですが、
一つ疑問が解消できてスッキリしました。

同じ疑問持っている人っているのかな…。

カテゴリー: ASP.NET, C#, VB, Web Forms | コメントする

HttpWebRequestクラスでハマったこと

お久しぶりです。

10月になって新環境で奮闘中です。
若干サボり気味でしたが、今後はちゃんと更新していきます…。

で、今回は前職でとあるWebサービスに対して負荷検証を行ったときに、
ハマったことと解決策を書いていきます。

やりたいこと

コンソールアプリケーションで以下のような処理を複数スレッドで実行して、
レスポンスが返ってくるまでの時間を計測します。

' リクエストで投げるデータをバイト変換
Dim postData As String = "POSTする値"
Dim postDataBytes As Byte() = Encoding.UTF8.GetBytes(postData)

' リクエストクラスの生成・設定
Dim request As HttpWebRequest = WebRequest.Create("リクエストを投げるURL")
With request
    .Method = "POST"
    .ContentType = "application/x-www-form-urlencoded"
    .ContentLength = postDataBytes.Length
End With

' リクエストクラスにバイト変換したデータを書き込む
Using stream As Stream = request.GetRequestStream
    With stream
        .Write(postDataBytes, 0, postDataBytes.Length)
        .Close()
    End With
End Using

' 時間計測開始
Dim sw As New Stopwatch
sw.Start()

' レスポンスを取得
Using response As WebResponse = request.GetResponse
    ' 時間計測終了
    sw.Stop()
    ' 処理時間を出力
    Console.Write(String.Format("{0}秒かかりました。", sw.ElapsedMilliseconds * 1000))
End Using

ハマったこと

①リクエストがなぜか2本ずつしか飛ばない

処理結果を見たときに2スレッドごとに数秒間隔があいてリクエストが飛ばされてました。
そこでコマンドプロンプトを立ち上げ、 netstat -a と入力して調べたところ、
2本ずつしか通信が行われていませんでした。

※よくよくデバッグで調べたところ、上記ソースの16行目で後続処理が止まるようです。

②各スレッドの初回リクエストを投げるときに時間がかかる

実施した負荷検証では上記のソースを1スレッドで数回行ったのですが、
初回の処理だけ5秒近く多く時間がかかっていました。

解決策

Google先生に聞いてみたところ、原因と解決策が載っていました。

①リクエストがなぜか2本ずつしか飛ばない

Asynchronous HTTPWebRequest, Maximum Connections – Best Approach – Threads or Delegates?

上記のリンク先に色々書いてありますが、
私の場合はServicePointManagerクラスのDefaultConnectionLimitプロパティの値を変えることで解決しました。

ServicePointManager.DefaultConnectionLimit プロパティ

既定値が2だったため、2本ずつしかリクエストが飛ばなかったようです。

ServicePointManager.DefaultConnectionLimit = (スレッド数)

と言う風にしておけば大丈夫でしょう。

②各スレッドの初回リクエストを投げるときに時間がかかる

.NET/WebRequestが初回実行時のみ遅い

プロキシ設定のようです。

HttpWebRequest.Proxy プロパティ

Dim request As HttpWebRequest = WebRequest.Create("リクエストを投げるURL")
With request
    .Method = "POST"
    .ContentType = "application/x-www-form-urlencoded"
    .ContentLength = postDataBytes.Length
    .Proxy = Nothing
End With

上記の6行目の設定を追加して解決しました。

ただこの初回時の処理が遅いと言うのは社内ネットワークから行った時のみで、
Windows Azure上に構築した仮想マシンからでは何ら問題はありませんでした。

※ネットワーク謎~。

まとめ

普段ASP.NETでWebアプリを開発してる上では特に使ったことない処理だったんですが、
今後バッチとかで使う機会がありそうなので備忘録として残しておきます。

同じことでハマった人に少しでも助けになれば良いです。

カテゴリー: .NET, VB | 1件のコメント

とりあえずブログ始めてみました

どうもみなさん初めまして。

某SIerで働いているしがないSEです。

(といっても来月から別会社で働きます。)

 

掲題の通り、とりあえずブログを始めてみました。

 

よく使う言語はASP.NET(VB.NET)とJavaScript(jQuery)、今はPHPをお勉強中です。

あとWindows Azureもよく触っています。

 

今まで技術調査とか仕事でつまづいた箇所の調査結果の共有や、

ASP.NETを基本的なところから見直してみるための備忘録という目的で、

このブログを書いていこうと思っています。

 

一つブログタイトルで誤解しないで頂きたいのは、

決してC#が嫌いと言うわけではなく、今までずっとVB使ってたから、

このタイトルにしたってだけのことです。

(現職はVBですが、次の職場ではC#を使う予定ですし…。)

 

とりあえず挨拶代わりに最初の記事を書いてみました。

今後、色々な情報をこのブログで発信していけたらと思います。

カテゴリー: Uncategorized | コメントする