by WebSurfer
2015年4月1日 16:30
ダウンロードするファイルをサーバー側で作成するのに時間がかかる場合、UpdatePanel と UpdateProgress コントロールを使って、ファイル作成中であることをユーザーに知らせる方法を書きます。
上の画像の一番上の行が表示された UpdateProgress です。この記事の例では、UpdateProgress が表示されるのはファイルをダウンロードしている時ではなく、その前のサーバー側でファイルを作成している時なのでご注意ください。
先の記事「ダウンロードは別ウィンドウで」に書いたような方法では、親ウィンドウの .aspx ページへの応答はすぐ帰ってくるものの、別ウィンドウを開いてそれから要求したダウンロード用 .aspx ページの応答はサーバー側でファイルの作成が完了するまで帰ってきません。
従って、ファイルの作成に時間がかかるとその間無反応になってしまい、ユーザーフレンドリーという面でどうかということになってしまいます。
少なくともサーバー側でファイルを作成している間その旨ユーザーに通知すれば、ユーザーのイライラも多少おさまるであろうということで、非同期要求と UpdateProgress を使った例を書いてみました。
どのような構成かを簡単に書くと、(1) .aspx ページに iframe と Button を含んだ UpdatePanel と UpdateProgress を配置、(2) Button クリックで非同期ポストバック、(3) サーバー側で Button クリックのハンドラでファイルを作成、(4) iframe の src 属性に作成したファイルを取得してダウンロードする HTTP ハンドラを設定・・・ということです。
そのようにすれば、非同期ポストバックをかけてから応答が帰ってくるまで UpdateProgress が表示され、応答が帰ってきて UpdatePanel 内が再描画されると、その中の iframe が src 属性に設定された HTTP ハンドラを要求し、HTTP ハンドラによってファイルがダウンロードされるという仕組みが作れます。
その .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 Button_Click(object sender, EventArgs e)
{
// 時間のかかるファイルの作成
System.Threading.Thread.Sleep(5000);
// ファイルの作成が完了したら、UpdatePanel 内に配置し
// た隠し iframe の src 属性に、作成したファイルを取
// 得してダウンロードする HTTP ハンドラを設定する。
// iframe には runat="server" 属性を付与してサーバー
// コントロールにしている点に注意。非同期ポストバック
// で UpdatePanel 内が再描画されると iframe から HTTP
// ハンドラが要求されファイルがダウンロードされる。
iframeDownload.Attributes["src"] =
"0104-TextFileDownloadHandler.ashx";
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script type="text/javascript">
//<![CDATA[
var manager;
function pageLoad(sender, args) {
if (args.get_isPartialLoad() === false) {
manager =
Sys.WebForms.PageRequestManager.getInstance();
manager.add_beginRequest(OnBeginRequest);
manager.add_endRequest(OnEndRequest);
}
}
function OnBeginRequest(sender, args) {
// 非同期要求の送信時に、アニメーションの表示などの
// スクリプトを起動する場合はここに設定。
}
function OnEndRequest(sender, args) {
// 完了時にスクリプトを起動する場合はここに設定。
}
// 実行中の非同期ポストバックを停止するスクリプト。
// UpdateProgress に配置したボタンの onclick に設定。
// サーバー側の処理まで停止されるわけではないので注意
function AbortPostBack() {
if (manager.get_isInAsyncPostBack()) {
manager.abortPostBack();
}
}
//]]>
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdateProgress ID="UpdateProgress1" runat="server"
AssociatedUpdatePanelID="UpdatePanel1">
<ProgressTemplate>
<asp:Image ID="Image1" runat="server"
ImageUrl="~/Images/grid-loading.gif" />
しばらくお待ちください・・・
<input type="button" onclick="AbortPostBack()"
value="Cancel" />
<br /><br />
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
UpdatePanel
<hr />
<%=DateTime.Now.ToString()%>
<br />
<asp:Button ID="Button1" runat="server"
Text="Download" OnClick="Button_Click" />
<br />
<%--ダウンロードに使う隠し iframe--%>
<iframe id="iframeDownload" runat="server"
style="visibility:hidden" />
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
上のコード例には、実行中の非同期ポストバックを停止するためのスクリプトその他をオマケ(?)で加えておきました。UpdatePanel を使うと他にもいろいろできますので、興味がありましたら MSDN ライブラリの記事 PageRequestManager のイベントの処理 を見てください。
iframe の src 属性に設定する HTTP ハンドラのコード例は、先の記事「ダウンロードは HTTP ハンドラで」を見てください。
.aspx ページを使っても可能ですが、その記事に書きましたようにいろいろ問題がありますので、HTTP ハンドラを使ったほうがよさそうです。
by WebSurfer
2012年8月10日 21:45
注意:
iframe に表示するページは親ページと同じドメインのものでないと、以下に紹介する方法では DOM は取得できないので注意してください(クロスサイトスクリプト対策だそうです)。
.NET Framework の WebBrowser に表示したページに iframe が含まれる場合、iframe の中の 要素 (HtmlElement) を取得するにはどうしたらいいかという話です。
ページが単一のドキュメントで構成されている場合は、WebBrowser.Document プロパティ で取得できる HtmlDocument オブジェクト ��ら、HtmlDocument.GetElementById メソッドなどを使って、ドキュメントを構成する要素 (HtmlElement) を取得できます。
しかしながら、ページに iframe が含まれている場合、HtmlDocument オブジェクトや、その All プロパティで取得できる HtmlElementCollection オブジェクトから iframe の中の要素を直接取得することはできません。(iframe は取得できますが、その子要素は取得できません)
iframe の中の子要素 (HtmlElement) を取得するには一工夫必要です。では、どうしたらいいでしょう?
例として、以下のように iframe を持ち、iframe の src 属性にテキストボックス 2 つとボタン 1 つを持つログインページページを指定してあるページで、iframe の中の要素 (HtmlElement) を取得して操作することを考えます。
親ページ(iframe を持つ)
<!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>
<title></title>
</head>
<body>
<h1>iframe 中の Login ページ</h1>
<iframe id="iframe1" src="168-Login.aspx" />
</body>
</html>
ログインページ(iframe に表示)
<%@ 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">
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
ID:<input id="Text1"
name="Text1"
type="text" />
<br />
PW:<input id="Text2"
name="Text2"
type="text" />
<br />
<input id="Submit1"
name="Submit1"
type="submit"
value="Login" />
</div>
</form>
</body>
</html>
上の「親ページ」を WebBrowser に表示したとします。
このとき、以下のコードでコメントアウトしたコードのように webBrowser1.Document.All で取得できる HtmlElementCollection には iframe の子要素は含まれないので id や pw は null になってしまいます。
iframe は WebBrowser コントロール内の別のウィンドウです。HtmlDocument.Window プロパティでドキュメントに関連付けられている HtmlWindow オブジェクトを取得し、HtmlWindow.Frames プロパティで Web ページ内で定義されている各 iframe 要素への参照を取得できます。(frame 要素の場合も同様です)
以下のようにして、iframe 内の要素 (HtmlElement) を取得して、操作することができます。
// これでは取得できない
//HtmlElement id =
// webBrowser1.Document.All.GetElementsByName("Text1")[0];
//HtmlElement pw =
// webBrowser1.Document.All.GetElementsByName("Text2")[0];
//id.InnerText = "loginName";
//pw.InnerText = "password";
//HtmlElement login =
// webBrowser1.Document.All.GetElementsByName("Submit1")[0];
//login.InvokeMember("click");
HtmlWindow iframe = webBrowser1.Document.Window.Frames[0];
HtmlElementCollection elements = iframe.Document.All;
HtmlElement id = elements.GetElementsByName("Text1")[0];
HtmlElement pw = elements.GetElementsByName("Text2")[0];
id.InnerText = "loginName";
pw.InnerText = "password";
HtmlElement login = elements.GetElementsByName("Submit1")[0];
login.InvokeMember("click");
by WebSurfer
2011年11月15日 22:35
SSL (Secure Sockets Layer) 通信を行っているページで、"セキュリティで保護されているコンテンツのみ表示されます。"(IE9 の場合。他のバージョン、ブラウザではメッセージが異なります。)という警告が表示されることがあります。
原因は、https:// で接続したページ内に https (SSL) と、http (非SSL) の接続が混在しているからです。例えば、そのページで使用している画像、CSS、JavaScript などのリソースへの参照を http で始まる URL で行っているケースです。
解決方法は、参照するファイルの URL を次のいずれかの形式に設定することです。
-
httpsで始まる絶対 URL パスを使う。
-
/ (スラッシュ)で始まるサイトルート相対パスを使う。
-
相対パスを使う。
上記のパスの具体的な例はそれぞれ以下のとおりです。
<img src="https://www.aaa.co.jp/images/sample.jpg" />
<img src="/images/sample.jpg" />
<img src="../images/sample.jpg" />
上記のようなケースは、html ソースを見れば気がつくので容易に対処できると思います。
気がつきにくいのが iframe の src 属性です。src を省略したり、http で始まる URL に設定したりすると警告が出ます。実は、自分は、これが問題になることすら知りませんでした。(汗)
特に問題なのは、動的に iframe がページに追加される場合です。さらに、ライブラリとして dll で提供されているカスタムコントロールの場合は、いくらソースを眺めていても、iframe は出てこないのでわかりません。
カスタムコントロールの具体的な例としては、AJAX Control Toolkit の AsyncFileUpload コントロールに使用されている iframe があります。このコントロール場合、src 属性には about:blank が設定されるのですが、about:blank も非 https と見なされるらしく、やはり警告が出ます。
解決方法は、KB261188 にあるように、iframe の src 属性の初期値を設定しない場合は、ダミー html ページを設定しておくことだそうです。
ただし、ダミーといっても存在しないファイルを設定するとサーバー側でエラーログが残るという話があるので(確かめたわけではありません)、中身は空でも実存するファイルを指定するのがよいそうです。
AJAX Control Toolkit の場合はソースが入手できますので、ソースを修正して再コンパイルするなりして解決できますが、そうでない場合は手の打ちようがないですね。